// SPDX-License-Identifier: MPL-2.0 // CameraService worker for the Nokia 2720 Flip. The parent broker drops this // process to Android's camera UID before exec. Gaia talks to it over a local // socket; it never receives Binder or elevated credentials. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool write_all(int fd, const void *data, size_t size) { const char *p = static_cast(data); while (size) { ssize_t n = write(fd, p, size); if (n <= 0) return false; p += n; size -= static_cast(n); } return true; } bool read_line(int fd, std::string *line) { line->clear(); char c; while (line->size() < 2048) { ssize_t n = read(fd, &c, 1); if (n <= 0) return false; if (c == '\n') return true; line->push_back(c); } return false; } bool send_json(int fd, const std::string &json, bool frame=false, uint32_t sequence=0, const std::vector *bytes=nullptr) { uint32_t length = static_cast(json.size()); if (frame) { uint32_t header[2] = {sequence, bytes ? static_cast(bytes->size()) : 0}; return write_all(fd, header, sizeof(header)) && (!bytes || write_all(fd,bytes->data(),bytes->size())); } return write_all(fd,&length,sizeof(length)) && write_all(fd,json.data(),json.size()); } class CameraWorker : public android::CameraListener { public: explicit CameraWorker(int fd) : fd_(fd), camera_id_(0), sequence_(0), stopping_(false) {} void notify(int32_t type, int32_t first, int32_t second) override { fprintf(stderr,"camera notify type=%d first=%d second=%d\n",type,first,second); } void postData(int32_t type, const android::sp &data, camera_frame_metadata_t *) override { if (data == nullptr) return; if (type == CAMERA_MSG_COMPRESSED_IMAGE) { std::lock_guard lock(mutex_); jpeg_.assign(static_cast(data->pointer()), static_cast(data->pointer()) + data->size()); ready_.notify_all(); return; } if (type != CAMERA_MSG_PREVIEW_FRAME) return; const size_t expected = 320u * 240u * 3u / 2u; if (data->size() < expected) return; { std::lock_guard lock(mutex_); frame_.assign(static_cast(data->pointer()), static_cast(data->pointer()) + expected); ++sequence_; if(sequence_==1) fprintf(stderr,"camera first frame bytes=%zu uid=%d\n",frame_.size(),getuid()); } ready_.notify_all(); } void postDataTimestamp(nsecs_t, int32_t, const android::sp &) override {} int run() { android::ProcessState::self()->startThreadPool(); std::string line; while (read_line(fd_, &line)) { if (line == "start" || line == "start 0") { if (!start()) { send_json(fd_,"{\"ready\":false,\"error\":\"CAMERA_START_FAILED\"}"); continue; } if (!send_json(fd_,"{\"ready\":true,\"width\":320,\"height\":240,\"format\":\"nv21\",\"orientation\":270,\"flashModes\":[\"off\",\"auto\",\"on\",\"torch\"],\"pictureSizes\":[{\"width\":1600,\"height\":1200}],\"focusModes\":[\"fixed\"],\"maxFocusAreas\":0,\"zoomRatios\":[1] }")) return 1; } else if (line == "frame") { std::vector frame; uint32_t seq; { std::unique_lock lock(mutex_); const uint32_t previous = delivered_; ready_.wait_for(lock,std::chrono::seconds(2),[&]{return sequence_>previous || stopping_;}); if (sequence_<=previous || frame_.empty()) { if(!send_json(fd_,"",true,sequence_,nullptr)) return 1; continue; } seq=sequence_; delivered_=seq; frame=frame_; } if (!send_json(fd_,"",true,seq,&frame)) return 1; } else if (line == "stop") { stop(); send_json(fd_,"{\"ok\":true}"); return 0; } else if (line.find("\"action\":\"flash\"") != std::string::npos) { const size_t key=line.find("\"mode\":\""); const size_t begin=key==std::string::npos?key:key+8; const size_t end=begin==std::string::npos?begin:line.find('"',begin); if(begin==std::string::npos || end==std::string::npos) { send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_INVALID_FLASH_MODE\"}");continue; } const std::string mode=line.substr(begin,end-begin); android::CameraParameters params(camera_->getParameters()); params.set(android::CameraParameters::KEY_FLASH_MODE,mode.c_str()); const android::status_t status=camera_->setParameters(params.flatten()); if(status==android::NO_ERROR) { camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA); camera_->startPreview(); send_json(fd_,"{\"ok\":true,\"result\":true}"); } else send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_FLASH_UNAVAILABLE\"}"); } else if (line.find("\"action\":\"zoom\"") != std::string::npos) { // This module reports a single zoom ratio (1x), so expose a stable // no-op instead of asking Camera1 to select an unsupported index. send_json(fd_,"{\"ok\":true,\"result\":{\"zoom\":1}}"); } else if (line.find("\"action\":\"picture\"") != std::string::npos) { { std::lock_guard lock(mutex_); jpeg_.clear(); } const android::status_t status=camera_->takePicture(CAMERA_MSG_COMPRESSED_IMAGE); std::vector jpeg; { std::unique_lock lock(mutex_); ready_.wait_for(lock,std::chrono::seconds(12),[&]{return !jpeg_.empty() || stopping_;}); jpeg=jpeg_; } camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA); camera_->startPreview(); if(status!=android::NO_ERROR || jpeg.empty()) { send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_CAPTURE_FAILED\"}");continue; } char reply[128]; std::snprintf(reply,sizeof(reply),"{\"ok\":true,\"jpegLength\":%zu}",jpeg.size()); if(!send_json(fd_,reply) || !write_all(fd_,jpeg.data(),jpeg.size())) return 1; } else { send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_UNSUPPORTED_COMMAND\"}"); } } stop(); return 0; } private: bool start() { if (camera_.get() != nullptr) stop(); fprintf(stderr,"camera count=%d uid=%d\n",android::Camera::getNumberOfCameras(),getuid()); camera_ = android::Camera::connect(camera_id_,android::String16("gonk.camera"),android::Camera::USE_CALLING_UID); if (camera_.get() == nullptr) {fprintf(stderr,"camera connect failed\n");return false;} android::CameraParameters params(camera_->getParameters()); params.setPreviewSize(320,240); params.setPictureSize(1600,1200); params.setPreviewFormat("yuv420sp"); params.set("recording-hint","false"); android::status_t status=camera_->setParameters(params.flatten()); fprintf(stderr,"camera set parameters=%d\n",status); if (status != android::NO_ERROR) { stop(); return false; } android::sp producer; android::sp consumer; android::sp allocator = new android::GraphicBufferAlloc(); android::BufferQueue::createBufferQueue(&producer,&consumer,allocator); consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER); consumer->setDefaultBufferSize(320,240); consumer_ = new android::CpuConsumer(consumer,4); consumer_->setName(android::String8("Vulpes Camera")); status=camera_->setPreviewTarget(producer); fprintf(stderr,"camera set target=%d\n",status); if (status != android::NO_ERROR) { stop(); return false; } camera_->setListener(this); // The broker consumes copied Camera1 preview frames rather than video metadata. camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA); status=camera_->startPreview(); fprintf(stderr,"camera start preview=%d\n",status); if (status != android::NO_ERROR) { stop(); return false; } stopping_=false; delivered_=sequence_; consumer_thread_=std::thread([this]{ while(!stopping_) { android::CpuConsumer::LockedBuffer buffer; if(consumer_->lockNextBuffer(&buffer)==android::NO_ERROR) consumer_->unlockBuffer(buffer); else usleep(10000); } }); return true; } void stop() { { std::lock_guard lock(mutex_); stopping_=true; } ready_.notify_all(); if (camera_.get() != nullptr) { camera_->stopPreview(); camera_->disconnect(); camera_.clear(); } if(consumer_thread_.joinable()) consumer_thread_.join(); consumer_.clear(); } int fd_; int camera_id_; android::sp camera_; android::sp consumer_; std::mutex mutex_; std::condition_variable ready_; std::thread consumer_thread_; std::vector frame_, jpeg_; uint32_t sequence_, delivered_; std::atomic stopping_; }; } int main() { setvbuf(stderr,nullptr,_IONBF,0); android::sp worker = new CameraWorker(STDIN_FILENO); return worker->run(); }