Release Vulpes OS 2.7-preview.24

This commit is contained in:
OverSu 2026-11-04 12:52:20 +01:00
commit 97ef99f827
135 changed files with 7717 additions and 2524 deletions

View file

@ -0,0 +1,54 @@
// SPDX-License-Identifier: MPL-2.0
// Fixed-command local broker; camera access is confined to Android UID 1006.
#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <grp.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SOCKET_DIR "/data/local/vulpes-beatles/camera-ipc"
#define SOCKET_PATH SOCKET_DIR "/camera.sock"
#define WORKER "/data/local/tmp/vulpes-beatles-camera-worker"
static volatile sig_atomic_t stopping;
static void stop_now(int signal_number) { (void)signal_number; stopping = 1; }
int main(void) {
if (setpgid(0,0) && errno != EACCES) return 1;
struct sigaction action = {0}; action.sa_handler = stop_now;
sigemptyset(&action.sa_mask); sigaction(SIGTERM,&action,NULL); sigaction(SIGINT,&action,NULL);
umask(0);
if (mkdir(SOCKET_DIR,0777) && errno != EEXIST) return 2;
chmod(SOCKET_DIR,0777); unlink(SOCKET_PATH);
int server=socket(AF_UNIX,SOCK_STREAM,0); if(server<0) return 3;
struct sockaddr_un address={0}; address.sun_family=AF_UNIX;
strncpy(address.sun_path,SOCKET_PATH,sizeof(address.sun_path)-1);
if(bind(server,(struct sockaddr *)&address,sizeof(address)) || chmod(SOCKET_PATH,0666) || listen(server,2)) return 4;
printf("camera broker ready uid=%d socket=%s\n",getuid(),SOCKET_PATH); fflush(stdout);
while(!stopping) {
int client=accept(server,NULL,NULL);
if(client<0) { if(errno==EINTR) continue; break; }
pid_t child=fork();
if(child==0) {
close(server);
fprintf(stderr,"camera worker accepted pid=%d\n",getpid()); fflush(stderr);
gid_t groups[]={1006};
if(setgroups(1,groups) || setgid(1006) || setuid(1006)) _exit(120);
if(dup2(client,STDIN_FILENO)<0 || dup2(client,STDOUT_FILENO)<0) _exit(121);
if(client>STDOUT_FILENO) close(client);
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
execl(WORKER,"vulpes-beatles-camera-worker",(char *)NULL);
_exit(127);
}
close(client);
while(waitpid(-1,NULL,WNOHANG)>0) {}
}
close(server); unlink(SOCKET_PATH); return 0;
}

View file

@ -0,0 +1,175 @@
// SPDX-License-Identifier: MPL-2.0
// Bounded Android API 23 Camera1 diagnostic using an EGL/GLConsumer output path.
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
#include <binder/ProcessState.h>
#include <gui/BufferQueue.h>
#include <gui/GLConsumer.h>
#include <gui/GraphicBufferAlloc.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <utils/String16.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#ifndef GL_TEXTURE_EXTERNAL_OES
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
static GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[1024];
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
fprintf(stderr, "shader_error=%s\n", log);
return 0;
}
return shader;
}
int main() {
setvbuf(stderr, nullptr, _IONBF, 0);
android::ProcessState::self()->startThreadPool();
const int cameraCount = android::Camera::getNumberOfCameras();
fprintf(stderr, "camera_count=%d uid=%d\n", cameraCount, getuid());
if (cameraCount < 1) return 2;
android::sp<android::Camera> camera = android::Camera::connect(
0, android::String16("org.vulpes.camera.glprobe"),
android::Camera::USE_CALLING_UID);
if (camera == nullptr) {
fprintf(stderr, "camera_connect_failed\n");
return 3;
}
android::CameraParameters parameters(camera->getParameters());
int width = 0;
int height = 0;
parameters.getPreviewSize(&width, &height);
fprintf(stderr, "stock_preview_size=%dx%d format=%s\n", width, height,
parameters.getPreviewFormat());
if (width <= 0 || height <= 0 || width > 1920 || height > 1920) return 4;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major = 0, minor = 0;
if (display == EGL_NO_DISPLAY || !eglInitialize(display, &major, &minor)) {
fprintf(stderr, "egl_initialize_error=%x\n", eglGetError());
return 5;
}
const EGLint configAttributes[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE};
EGLConfig config;
EGLint configCount = 0;
if (!eglChooseConfig(display, configAttributes, &config, 1, &configCount) ||
configCount < 1) return 6;
const EGLint surfaceAttributes[] = {
EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE};
EGLSurface surface = eglCreatePbufferSurface(display, config, surfaceAttributes);
const EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT,
contextAttributes);
if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT ||
!eglMakeCurrent(display, surface, surface, context)) {
fprintf(stderr, "egl_context_error=%x\n", eglGetError());
return 7;
}
GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
android::sp<android::IGraphicBufferProducer> producer;
android::sp<android::IGraphicBufferConsumer> consumer;
android::sp<android::GraphicBufferAlloc> allocator =
new android::GraphicBufferAlloc();
android::BufferQueue::createBufferQueue(&producer, &consumer, allocator);
consumer->setDefaultBufferSize(width, height);
android::sp<android::GLConsumer> glConsumer = new android::GLConsumer(
consumer, texture, GL_TEXTURE_EXTERNAL_OES, true, false);
const int targetStatus = camera->setPreviewTarget(producer);
fprintf(stderr, "set_preview_target=%d\n", targetStatus);
const int startStatus = camera->startPreview();
fprintf(stderr, "start_preview=%d\n", startStatus);
if (targetStatus || startStatus) return 8;
const char* vertexSource =
"attribute vec4 a; attribute vec2 t; varying vec2 v;"
"void main(){gl_Position=a;v=t;}";
const char* fragmentSource =
"#extension GL_OES_EGL_image_external : require\n"
"precision mediump float; uniform samplerExternalOES s; varying vec2 v;"
"void main(){gl_FragColor=texture2D(s,v);}";
GLuint program = glCreateProgram();
GLuint vertex = compileShader(GL_VERTEX_SHADER, vertexSource);
GLuint fragment = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
if (!vertex || !fragment) return 9;
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glBindAttribLocation(program, 0, "a");
glBindAttribLocation(program, 1, "t");
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
char log[1024];
glGetProgramInfoLog(program, sizeof(log), nullptr, log);
fprintf(stderr, "program_link_error=%s\n", log);
return 10;
}
const GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
const GLfloat texcoords[] = {0, 1, 1, 1, 0, 0, 1, 0};
glUseProgram(program);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoords);
glEnableVertexAttribArray(1);
glUniform1i(glGetUniformLocation(program, "s"), 0);
bool captured = false;
for (int attempt = 0; attempt < 100 && !captured; ++attempt) {
usleep(100000);
const int updateStatus = glConsumer->updateTexImage();
if (updateStatus != 0) continue;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFinish();
unsigned char* pixels = new unsigned char[width * height * 3];
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
const GLenum glError = glGetError();
if (glError == GL_NO_ERROR) {
FILE* output = fopen("/data/local/tmp/vulpes-camera-glconsumer.ppm", "wb");
if (output) {
fprintf(output, "P6\n%d %d\n255\n", width, height);
for (int y = height - 1; y >= 0; --y)
fwrite(pixels + y * width * 3, 3, width, output);
fclose(output);
chmod("/data/local/tmp/vulpes-camera-glconsumer.ppm", 0644);
captured = true;
}
}
delete[] pixels;
fprintf(stderr, "update=%d gl_error=%x captured=%d\n",
updateStatus, glError, captured);
}
fprintf(stderr, "stopping_preview\n");
camera->stopPreview();
fprintf(stderr, "disconnecting_camera\n");
camera->disconnect();
fprintf(stderr, "captured=%d\n", captured);
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
eglDestroySurface(display, surface);
eglTerminate(display);
return captured ? 0 : 11;
}

View file

@ -0,0 +1,213 @@
// SPDX-License-Identifier: MPL-2.0
// RAM-only diagnostic reproducing Gecko's Android 6 GonkBufferQueue path.
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
#include <system/camera.h>
#include <binder/ProcessState.h>
#include <gui/GraphicBufferAlloc.h>
#include <gui/IConsumerListener.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
#include <utils/String16.h>
#include <utils/Mutex.h>
#include <utils/Vector.h>
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <utils/Timers.h>
#include <sys/stat.h>
#define ANDROID_VERSION 23
#include "GonkBufferQueueLL.h"
#include "GonkBufferQueueProducer.h"
class CameraEvents : public android::CameraListener {
public:
void notify(int32_t type, int32_t ext1, int32_t ext2) override {
fprintf(stderr, "camera_notify=%d,%d,%d\n", type, ext1, ext2);
}
void postData(int32_t type, const android::sp<android::IMemory>& memory,
camera_frame_metadata_t*) override {
if (memory == nullptr) return;
const size_t size = memory->size();
const unsigned char* bytes = static_cast<const unsigned char*>(memory->pointer());
unsigned int low = 255, high = 0;
uint64_t sum = 0;
const size_t ySize = size < 640u * 480u ? size : 640u * 480u;
for (size_t i = 0; i < ySize; ++i) {
if (bytes[i] < low) low = bytes[i];
if (bytes[i] > high) high = bytes[i];
sum += bytes[i];
}
fprintf(stderr, "camera_data=%d size=%zu y=%u..%u mean=%.2f\n",
type, size, low, high, ySize ? (double)sum / ySize : 0);
static bool saved = false;
if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
FILE* jpeg = fopen("/data/local/tmp/vulpes-camera-callback.jpg", "wb");
if (jpeg) { fwrite(bytes, 1, size, jpeg); fclose(jpeg); chmod("/data/local/tmp/vulpes-camera-callback.jpg", 0644); }
fprintf(stderr, "captured_jpeg=%zu\n", size);
} else if (!saved) {
FILE* output = fopen("/data/local/tmp/vulpes-camera-callback.yuv", "wb");
if (output) { fwrite(bytes, 1, size, output); fclose(output); chmod("/data/local/tmp/vulpes-camera-callback.yuv", 0644); saved = true; }
}
}
void postDataTimestamp(nsecs_t, int32_t type,
const android::sp<android::IMemory>&) override {
fprintf(stderr, "camera_timestamp_data=%d\n", type);
}
};
class QueueListener : public android::ConsumerListener {
public:
void onFrameAvailable(const android::BufferItem&) override {}
void onFrameReplaced(const android::BufferItem&) override {}
void onBuffersReleased() override {}
void onSidebandStreamChanged() override {}
};
int main() {
setvbuf(stderr, nullptr, _IONBF, 0);
android::ProcessState::self()->startThreadPool();
const int count = android::Camera::getNumberOfCameras();
fprintf(stderr, "camera_count=%d uid=%d\n", count, getuid());
if (count < 1) return 2;
android::sp<android::Camera> camera = android::Camera::connect(
// Match GonkCameraHwMgr.cpp exactly. CameraService/AppOps on this
// firmware treats the client package as part of camera authorization.
0, android::String16("gonk.camera"),
android::Camera::USE_CALLING_UID);
if (camera == nullptr) return 3;
android::CameraParameters params(camera->getParameters());
android::Vector<android::Size> previews, pictures;
params.getSupportedPreviewSizes(previews);
params.getSupportedPictureSizes(pictures);
android::Size picture = {0, 0};
for (size_t i = 0; i < pictures.size(); ++i)
if (pictures[i].width * pictures[i].height > picture.width * picture.height)
picture = pictures[i];
android::Size preview = {0, 0};
uint64_t bestDelta = UINT64_MAX;
for (size_t i = 0; i < previews.size(); ++i) {
const android::Size candidate = previews[i];
if (picture.width && candidate.width * picture.height != picture.width * candidate.height)
continue;
const uint64_t area = (uint64_t)candidate.width * candidate.height;
const uint64_t delta = area > 640u * 480u ? area - 640u * 480u : 640u * 480u - area;
if (delta < bestDelta) { preview = candidate; bestDelta = delta; }
}
if (!preview.width && previews.size()) preview = previews[0];
if (!preview.width || !picture.width) {
fprintf(stderr, "no supported preview/picture size (preview=%zu picture=%zu)\n",
previews.size(), pictures.size());
camera->disconnect();
return 4;
}
params.setPreviewSize(preview.width, preview.height);
params.setPictureSize(picture.width, picture.height);
// Gecko applies its recording hint after selecting still/preview sizes and
// before starting the preview. Keep the probe's Camera1 state in step.
params.set("recording-hint", "false");
android::String8 configured = params.flatten();
android::status_t status = camera->setParameters(configured);
fprintf(stderr, "set_parameters=%d requested_preview=%dx%d picture=%dx%d\n",
status, preview.width, preview.height, picture.width, picture.height);
if (status != android::NO_ERROR) { camera->disconnect(); return 4; }
params.unflatten(camera->getParameters());
int width = 0, height = 0;
params.getPreviewSize(&width, &height);
int pictureWidth = 0, pictureHeight = 0;
params.getPictureSize(&pictureWidth, &pictureHeight);
fprintf(stderr, "preview=%dx%d picture=%dx%d format=%s\n", width, height,
pictureWidth, pictureHeight, params.getPreviewFormat());
android::sp<android::IGraphicBufferProducer> producer;
android::sp<android::IGonkGraphicBufferConsumer> consumer;
android::sp<android::GraphicBufferAlloc> allocator =
new android::GraphicBufferAlloc();
android::GonkBufferQueue::createBufferQueue(&producer, &consumer, allocator);
status = static_cast<android::GonkBufferQueueProducer*>(producer.get())
->setSynchronousMode(false);
fprintf(stderr, "async_producer=%d\n", status);
status = consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN);
fprintf(stderr, "cpu_read_usage=%d\n", status);
status = consumer->setMaxAcquiredBufferCount(4);
fprintf(stderr, "max_acquired=%d\n", status);
android::sp<QueueListener> listener = new QueueListener();
android::sp<android::IConsumerListener> proxy =
new android::GonkBufferQueue::ProxyConsumerListener(listener);
status = consumer->consumerConnect(proxy, false);
fprintf(stderr, "consumer_connect=%d\n", status);
android::sp<CameraEvents> cameraEvents = new CameraEvents();
camera->setListener(cameraEvents);
status = camera->setPreviewTarget(producer);
fprintf(stderr, "set_preview_target=%d\n", status);
camera->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
fprintf(stderr, "preview_callback_flags=%d\n", CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
const android::status_t focusEvents = camera->sendCommand(
CAMERA_CMD_ENABLE_FOCUS_MOVE_MSG, 1, 0);
fprintf(stderr, "focus_move_events=%d\n", focusEvents);
status = camera->startPreview();
fprintf(stderr, "start_preview=%d\n", status);
if (status) return 4;
unsigned acquired = 0;
bool saved = false;
const int64_t deadline = systemTime(SYSTEM_TIME_MONOTONIC) + 5000000000LL;
while (systemTime(SYSTEM_TIME_MONOTONIC) < deadline) {
android::IGonkGraphicBufferConsumer::BufferItem item;
status = consumer->acquireBuffer(&item, 0);
if (status != android::NO_ERROR) { usleep(20000); continue; }
++acquired;
android::sp<android::GraphicBuffer> buffer = item.mGraphicBuffer;
if (item.mFence != nullptr) item.mFence->waitForever("vulpes-gonk-camera");
void* pixels = nullptr;
if (buffer != nullptr &&
buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels) == android::NO_ERROR &&
pixels != nullptr) {
const size_t bytes = (size_t)buffer->getStride() * buffer->getHeight() * 3 / 2;
const unsigned char* y = static_cast<const unsigned char*>(pixels);
unsigned int minimum = 255, maximum = 0;
uint64_t sum = 0;
const size_t yBytes = (size_t)buffer->getStride() * buffer->getHeight();
for (size_t i = 0; i < yBytes; ++i) {
if (y[i] < minimum) minimum = y[i];
if (y[i] > maximum) maximum = y[i];
sum += y[i];
}
fprintf(stderr, "frame=%u number=%llu timestamp=%llu y=%u..%u mean=%.2f format=%d\n",
acquired, (unsigned long long)item.mFrameNumber, (unsigned long long)item.mTimestamp, minimum, maximum,
(double)sum / yBytes, buffer->format);
if (!saved || acquired % 10 == 0) {
FILE* output = fopen("/data/local/tmp/vulpes-camera-gonkqueue.nv21", "wb");
if (output) {
fwrite(pixels, 1, bytes, output);
fclose(output);
chmod("/data/local/tmp/vulpes-camera-gonkqueue.nv21", 0644);
FILE* meta = fopen("/data/local/tmp/vulpes-camera-gonkqueue.txt", "w");
if (meta) {
fprintf(meta, "%u %u %u %d %zu %u %u %u %u\n", buffer->getWidth(),
buffer->getHeight(), buffer->getStride(), buffer->format, bytes,
acquired, minimum, maximum, (unsigned)(sum / yBytes));
fclose(meta);
chmod("/data/local/tmp/vulpes-camera-gonkqueue.txt", 0644);
}
saved = true;
}
}
buffer->unlock();
}
consumer->releaseBuffer(item.mBuf, item.mFrameNumber, android::Fence::NO_FENCE);
}
fprintf(stderr, "acquired=%u saved=%d; taking_still\n", acquired, saved);
status = camera->takePicture(CAMERA_MSG_SHUTTER | CAMERA_MSG_COMPRESSED_IMAGE);
fprintf(stderr, "take_picture=%d\n", status);
sleep(3);
fprintf(stderr, "stopping_preview\n");
camera->stopPreview();
fprintf(stderr, "camera_stopped; disconnecting\n");
camera->disconnect();
consumer->consumerDisconnect();
fprintf(stderr, "done\n");
return saved ? 0 : 5;
}

View file

@ -0,0 +1,99 @@
// SPDX-License-Identifier: MPL-2.0
// Temporary, narrowly scoped API-23 permission controller for a RAM camera test.
#include <binder/Binder.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <camera/ICameraService.h>
#include <grp.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
#include <unistd.h>
#include <stdio.h>
namespace {
const android::String16 kDescriptor("android.os.IPermissionController");
const android::String16 kCameraPermission("android.permission.CAMERA");
const android::String16 kSystemEventPermission("android.permission.CAMERA_SEND_SYSTEM_EVENTS");
const android::String16 kProbePackage("gonk.camera");
const android::String16 kSystemPackage("android");
const int32_t kCameraUid = 1006;
class CameraPermissionBridge : public android::BBinder {
public:
const android::String16& getInterfaceDescriptor() const override {
return kDescriptor;
}
protected:
android::status_t onTransact(uint32_t code, const android::Parcel& data,
android::Parcel* reply, uint32_t flags) override {
if (code == android::IBinder::FIRST_CALL_TRANSACTION) {
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
const android::String16 permission = data.readString16();
const int32_t pid = data.readInt32();
const int32_t uid = data.readInt32();
(void)pid;
const bool grant = (permission == kCameraPermission && uid == kCameraUid) ||
(permission == kSystemEventPermission && uid == 1000);
android::String8 permissionName(permission);
fprintf(stderr, "permission check uid=%d name=%s grant=%d\n", uid,
permissionName.string(), grant);
reply->writeNoException();
reply->writeInt32(grant ? 1 : 0);
return android::NO_ERROR;
}
if (code == android::IBinder::FIRST_CALL_TRANSACTION + 1) {
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
const int32_t uid = data.readInt32();
reply->writeNoException();
const bool hasPackage = uid == kCameraUid || uid == 1000;
reply->writeInt32(hasPackage ? 1 : 0);
if (uid == kCameraUid) reply->writeString16(kProbePackage);
else if (uid == 1000) reply->writeString16(kSystemPackage);
return android::NO_ERROR;
}
if (code == android::IBinder::FIRST_CALL_TRANSACTION + 2) {
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
const android::String16 permission = data.readString16();
reply->writeNoException();
reply->writeInt32(permission == kCameraPermission ? 1 : 0);
return android::NO_ERROR;
}
return android::BBinder::onTransact(code, data, reply, flags);
}
};
}
int main(int argc, char**) {
if (argc != 1) return 2;
android::ProcessState::self()->startThreadPool();
android::sp<android::IServiceManager> manager = android::defaultServiceManager();
android::sp<CameraPermissionBridge> service = new CameraPermissionBridge();
android::status_t result = manager->addService(android::String16("permission"), service);
fprintf(stderr, "register permission bridge pid=%d uid=%d result=%d\n",
getpid(), getuid(), result);
if (result != android::NO_ERROR) return 3;
gid_t systemGroup = 1000;
if (setgroups(1, &systemGroup) || setgid(1000) || setuid(1000)) {
perror("drop permission bridge to Android system UID");
return 4;
}
fprintf(stderr, "permission bridge ready\n");
android::sp<android::IBinder> cameraBinder =
manager->checkService(android::String16("media.camera"));
android::sp<android::ICameraService> cameraService =
android::interface_cast<android::ICameraService>(cameraBinder);
if (cameraService != nullptr) {
const int32_t activeUsers[] = {0};
cameraService->notifySystemEvent(android::ICameraService::USER_SWITCHED,
activeUsers, 1);
fprintf(stderr, "notified CameraService active user 0\n");
} else {
fprintf(stderr, "CameraService unavailable for active-user notification\n");
}
android::IPCThreadState::self()->joinThreadPool();
return 0;
}

View file

@ -0,0 +1,173 @@
// SPDX-License-Identifier: MPL-2.0
// Bounded diagnostic for the Android Camera1 service on Nokia 2720 (API 23).
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
#include <hardware/gralloc.h>
#include <binder/ProcessState.h>
#include <gui/BufferQueue.h>
#include <gui/CpuConsumer.h>
#include <gui/GraphicBufferAlloc.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Mutex.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <thread>
class ProbeListener : public android::CameraListener {
public:
ProbeListener() : frames(0), bytes(0), jpegBytes(0), yMean(0), yMin(0), yMax(0) {}
void notify(int32_t type, int32_t first, int32_t second) override {
fprintf(stderr, "notify type=%d first=%d second=%d\n", type, first, second);
}
void postData(int32_t type, const android::sp<android::IMemory>& data,
camera_frame_metadata_t*) override {
if (data == nullptr) return;
if (type == CAMERA_MSG_PREVIEW_FRAME) {
android::Mutex::Autolock lock(mutex);
++frames;
bytes = data->size();
const size_t yBytes = bytes < 640u * 480u ? bytes : 640u * 480u;
const unsigned char* y = static_cast<const unsigned char*>(data->pointer());
uint64_t sum = 0;
yMin = 255;
yMax = 0;
for (size_t i = 0; i < yBytes; ++i) {
if (y[i] < yMin) yMin = y[i];
if (y[i] > yMax) yMax = y[i];
sum += y[i];
}
yMean = yBytes ? (double)sum / yBytes : 0;
if (frames == 20 && bytes) {
FILE* out = fopen("/data/local/tmp/vulpes-camera-frame20.nv21", "wb");
if (out) {
fwrite(data->pointer(), 1, bytes, out);
fclose(out);
chmod("/data/local/tmp/vulpes-camera-frame20.nv21", 0644);
}
}
} else if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
FILE* out = fopen("/data/local/tmp/vulpes-camera-photo.jpg", "wb");
if (out) {
jpegBytes = fwrite(data->pointer(), 1, data->size(), out);
fclose(out);
chmod("/data/local/tmp/vulpes-camera-photo.jpg", 0644);
}
fprintf(stderr, "jpeg_callback_bytes=%zu\n", jpegBytes);
}
}
void postDataTimestamp(nsecs_t, int32_t,
const android::sp<android::IMemory>&) override {}
android::Mutex mutex;
unsigned frames;
size_t bytes;
size_t jpegBytes;
double yMean;
unsigned yMin;
unsigned yMax;
};
int main(int argc, char** argv) {
if (argc > 2) return 2;
setvbuf(stderr, nullptr, _IONBF, 0);
const int cameraId = argc == 2 ? atoi(argv[1]) : 0;
android::ProcessState::self()->startThreadPool();
const int count = android::Camera::getNumberOfCameras();
fprintf(stderr, "camera_count=%d uid=%d\n", count, getuid());
if (cameraId < 0 || cameraId >= count) return 3;
android::CameraInfo info;
int status = android::Camera::getCameraInfo(cameraId, &info);
fprintf(stderr, "camera_info_status=%d facing=%d orientation=%d\n",
status, info.facing, info.orientation);
android::sp<android::Camera> camera = android::Camera::connect(
cameraId, android::String16("gonk.camera"),
android::Camera::USE_CALLING_UID);
if (camera == nullptr) {
fprintf(stderr, "camera_connect_failed\n");
return 4;
}
android::CameraParameters configured(camera->getParameters());
// Stock KaiOS Camera reports app.camera.givenPreviewSize as 320x240
// on the beatles device. Gecko then selects this 4:3 size against the
// 1600x1200 still capture size. Keep the probe on that exact path.
configured.setPreviewSize(320, 240);
configured.setPictureSize(1600, 1200);
configured.setPreviewFormat("yuv420sp");
configured.set("recording-hint", "false");
status = camera->setParameters(configured.flatten());
fprintf(stderr, "set_full_parameters=%d\n", status);
android::sp<ProbeListener> listener = new ProbeListener();
camera->setListener(listener);
camera->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK);
android::String8 parameters = camera->getParameters();
fprintf(stderr, "camera_parameters=%s\n", parameters.string());
fprintf(stderr, "before_bufferqueue_create\n");
android::sp<android::IGraphicBufferProducer> producer;
android::sp<android::IGraphicBufferConsumer> consumer;
android::sp<android::GraphicBufferAlloc> allocator = new android::GraphicBufferAlloc();
android::BufferQueue::createBufferQueue(&producer, &consumer, allocator);
fprintf(stderr, "after_bufferqueue_create producer=%d consumer=%d\n", producer != nullptr, consumer != nullptr);
fprintf(stderr, "before_cpuconsumer\n");
android::sp<android::CpuConsumer> previewConsumer =
new android::CpuConsumer(consumer, 4);
fprintf(stderr, "after_cpuconsumer\n");
status = consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER);
fprintf(stderr, "set_consumer_usage=%d bits=%08x\n", status,
(unsigned)(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER));
fprintf(stderr, "before_consumer_setname\n");
previewConsumer->setName(android::String8("Vulpes Camera Probe"));
fprintf(stderr, "after_consumer_setname\n");
fprintf(stderr, "before_consumer_size\n");
previewConsumer->setDefaultBufferSize(320, 240);
fprintf(stderr, "after_consumer_size\n");
fprintf(stderr, "before_set_preview_target\n");
status = camera->setPreviewTarget(producer);
fprintf(stderr, "set_preview_target=%d\n", status);
status = camera->startPreview();
fprintf(stderr, "start_preview=%d\n", status);
unsigned graphicFrames = 0, graphicSamples = 0;
unsigned long long graphicSum = 0;
unsigned graphicMin = 255, graphicMax = 0;
for (int elapsed = 0; elapsed < 3; elapsed++) {
android::CpuConsumer::LockedBuffer buffer;
while (previewConsumer->lockNextBuffer(&buffer) == android::NO_ERROR) {
++graphicFrames;
fprintf(stderr, "graphic_frame=%u width=%u height=%u stride=%u format=%d\n",
graphicFrames, buffer.width, buffer.height, buffer.stride,
buffer.format);
if (buffer.data && graphicFrames >= 5) {
const size_t yBytes = (size_t)buffer.stride * buffer.height;
if (graphicFrames == 5) {
FILE* out = fopen("/data/local/tmp/vulpes-camera-graphic.nv21", "wb");
if (out) { fwrite(buffer.data, 1, yBytes * 3 / 2, out); fclose(out); chmod("/data/local/tmp/vulpes-camera-graphic.nv21", 0644); }
}
for (size_t i = 0; i < yBytes; i += 4096) {
unsigned value = buffer.data[i]; graphicSum += value; ++graphicSamples;
if (value < graphicMin) graphicMin = value;
if (value > graphicMax) graphicMax = value;
}
}
previewConsumer->unlockBuffer(buffer);
}
usleep(1000000);
}
{
android::Mutex::Autolock lock(listener->mutex);
fprintf(stderr, "preview_frames=%u frame_bytes=%zu callback_y_mean=%.2f min=%u max=%u graphic_frames=%u graphic_samples=%u graphic_y_mean=%.2f min=%u max=%u\n",
listener->frames, listener->bytes, listener->yMean, listener->yMin, listener->yMax,
graphicFrames, graphicSamples,
graphicSamples ? (double)graphicSum / graphicSamples : 0.0,
graphicMin, graphicMax);
}
std::thread([camera]() {
fprintf(stderr, "take_picture_begin\n");
const int pictureStatus = camera->takePicture(CAMERA_MSG_COMPRESSED_IMAGE);
fprintf(stderr, "take_picture=%d\n", pictureStatus);
}).detach();
sleep(4);
camera->stopPreview();
camera->disconnect();
return 0;
}

View file

@ -0,0 +1,214 @@
// 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 <camera/Camera.h>
#include <camera/CameraParameters.h>
#include <binder/ProcessState.h>
#include <gui/BufferQueue.h>
#include <gui/CpuConsumer.h>
#include <gui/GraphicBufferAlloc.h>
#include <hardware/gralloc.h>
#include <utils/Timers.h>
#include <utils/Mutex.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <condition_variable>
#include <chrono>
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <unistd.h>
#include <sys/socket.h>
namespace {
bool write_all(int fd, const void *data, size_t size) {
const char *p = static_cast<const char *>(data);
while (size) {
ssize_t n = write(fd, p, size);
if (n <= 0) return false;
p += n; size -= static_cast<size_t>(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<uint8_t> *bytes=nullptr) {
uint32_t length = static_cast<uint32_t>(json.size());
if (frame) {
uint32_t header[2] = {sequence, bytes ? static_cast<uint32_t>(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<android::IMemory> &data,
camera_frame_metadata_t *) override {
if (data == nullptr) return;
if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
std::lock_guard<std::mutex> lock(mutex_);
jpeg_.assign(static_cast<const uint8_t *>(data->pointer()),
static_cast<const uint8_t *>(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<std::mutex> lock(mutex_);
frame_.assign(static_cast<const uint8_t *>(data->pointer()),
static_cast<const uint8_t *>(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<android::IMemory> &) 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<uint8_t> frame;
uint32_t seq;
{
std::unique_lock<std::mutex> 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<std::mutex> lock(mutex_); jpeg_.clear(); }
const android::status_t status=camera_->takePicture(CAMERA_MSG_COMPRESSED_IMAGE);
std::vector<uint8_t> jpeg;
{
std::unique_lock<std::mutex> 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<android::IGraphicBufferProducer> producer;
android::sp<android::IGraphicBufferConsumer> consumer;
android::sp<android::GraphicBufferAlloc> 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<std::mutex> 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<android::Camera> camera_;
android::sp<android::CpuConsumer> consumer_;
std::mutex mutex_; std::condition_variable ready_;
std::thread consumer_thread_;
std::vector<uint8_t> frame_, jpeg_; uint32_t sequence_, delivered_; std::atomic<bool> stopping_;
};
}
int main() {
setvbuf(stderr,nullptr,_IONBF,0);
android::sp<CameraWorker> worker = new CameraWorker(STDIN_FILENO);
return worker->run();
}

View file

@ -31,6 +31,7 @@
#include "beatles-lid.h"
static volatile sig_atomic_t interrupted;
static int phone_controls;
static int camera_broker_active;
static void interrupt(int sig) { interrupted = sig; }
static void inventory_camera_nodes(void) {
static const int minors[] = {0, 1, 32, 33};
@ -238,6 +239,11 @@ static void inside(int seconds) {
require(mount("/proc", ROOT "/proc", NULL, MS_BIND, NULL), "bind proc");
require(mount("/sys", ROOT "/sys", NULL, MS_BIND, NULL), "bind sys");
require(mount(NULL, ROOT "/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL), "readonly sys");
if (camera_broker_active) {
if (mkdir(ROOT "/work/camera-ipc",0777) && errno!=EEXIST) _exit(1);
require(mount(BASE "/camera-ipc",ROOT "/work/camera-ipc",NULL,MS_BIND,NULL),"camera IPC mount");
require(chmod(ROOT "/work/camera-ipc",0777),"camera IPC permissions");
}
require(mount("tmpfs", ROOT "/dev", "tmpfs", MS_NOSUID, "size=1m,mode=0755"), "devices");
node(ROOT "/dev/null",1,3,0666); node(ROOT "/dev/zero",1,5,0666);
node(ROOT "/dev/random",1,8,0666); node(ROOT "/dev/urandom",1,9,0666);
@ -261,6 +267,7 @@ static void inside(int seconds) {
setenv("VULPES_TRIAL_MANUAL",seconds >= 900 ? "1" : "0",1);
setenv("VULPES_TUNDRA_RADIO",phone_controls?"1":"0",1);
setenv("VULPES_TUNDRA_WIFI",phone_controls?"1":"0",1);
setenv("VULPES_TUNDRA_CAMERA",camera_broker_active?"1":"0",1);
setenv("LANG","C",1); setenv("DISPLAY",":0",1);
setenv("LD_LIBRARY_PATH","/usr/lib/arm-linux-gnueabihf:/lib/arm-linux-gnueabihf",1);
execl("/bin/sh","sh","/work/display-test.sh",NULL);
@ -272,6 +279,7 @@ int main(int argc, char **argv) {
int seconds = !strcmp(argv[1],"--temporary-45s") ? 45 :
!strcmp(argv[1],"--temporary-90s") ? 90 :
(!strcmp(argv[1],"--temporary-180s") || !strcmp(argv[1],"--phone-180s")) ? 180 :
!strcmp(argv[1],"--temporary-240s") ? 240 :
(!strcmp(argv[1],"--interactive-900s") || !strcmp(argv[1],"--phone-900s")) ? 900 :
(!strcmp(argv[1],"--interactive-1800s") || !strcmp(argv[1],"--phone-1800s")) ? 1800 : 0;
if (!seconds) return 2;
@ -309,6 +317,17 @@ int main(int argc, char **argv) {
* previous run's image be mistaken for a frame from this session. */
unlink(ROOT "/work/camera-frame.yuv");
unlink(ROOT "/work/camera-photo.jpg");
/* These names belong to our API-23 diagnostic; adb shell cannot remove
* camera-UID files from Android's sticky /data/local/tmp directory. */
unlink("/data/local/tmp/vulpes-camera-frame.nv21");
unlink("/data/local/tmp/vulpes-camera-frame20.nv21");
unlink("/data/local/tmp/vulpes-camera-graphic.nv21");
unlink("/data/local/tmp/vulpes-camera-photo.jpg");
unlink("/data/local/tmp/vulpes-camera-glconsumer.ppm");
unlink("/data/local/tmp/vulpes-camera-gonkqueue.nv21");
unlink("/data/local/tmp/vulpes-camera-gonkqueue.txt");
unlink("/data/local/tmp/vulpes-camera-callback.yuv");
unlink("/data/local/tmp/vulpes-camera-callback.jpg");
sleep(2);
if (!access(BASE "/camera-probe-once", F_OK)) {
unlink(BASE "/camera-probe-once");
@ -326,12 +345,80 @@ int main(int argc, char **argv) {
}
int result = android_service("/system/bin/stop");
printf("stop b2g: %d\n",result);
pid_t child = -1, radio = -1;
pid_t child = -1, radio = -1, permission_bridge = -1, camera_broker = -1;
struct lid_state lid;lid_init(&lid,fb);
if (!result) {
sleep(1);
puts("Camera node inventory after stopping B2G");
inventory_camera_nodes();
int gl_camera_probe = !access(BASE "/camera-gl-probe-once", F_OK);
int camera_service_probe = !access(BASE "/camera-service-probe-once", F_OK);
if (gl_camera_probe || camera_service_probe) {
unlink(BASE "/camera-service-probe-once");
unlink(BASE "/camera-gl-probe-once");
permission_bridge = fork();
if (!permission_bridge) {
int output = open(BASE "/camera-permission-bridge.log",
O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (output >= 0) {
fchmod(output, 0644);
dup2(output, STDOUT_FILENO);
dup2(output, STDERR_FILENO);
close(output);
}
setenv("LD_LIBRARY_PATH", "/system/lib:/data/local/tmp", 1);
execl("/data/local/tmp/vulpes-camera-permission-bridge",
"vulpes-camera-permission-bridge", NULL);
_exit(127);
}
if (permission_bridge > 0) sleep(2);
pid_t probe = fork();
if (!probe) {
int output = open(gl_camera_probe ? BASE "/camera-gl-probe.log" :
BASE "/camera-service-probe.log",
O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (output >= 0) {
fchmod(output, 0644);
dup2(output, STDOUT_FILENO);
dup2(output, STDERR_FILENO);
close(output);
}
gid_t groups[] = {1006};
if (setgroups(1, groups) || setgid(1006) || setuid(1006)) _exit(126);
setenv("LD_LIBRARY_PATH", "/system/lib:/data/local/tmp", 1);
if (gl_camera_probe)
execl("/data/local/tmp/vulpes-camera-gl-probe-20261023",
"vulpes-camera-gl-probe-20261023", NULL);
else
execl("/data/local/tmp/vulpes-camera-probe-20261023",
"vulpes-camera-probe-20261023", "0", NULL);
_exit(127);
}
if (probe > 0) {
int probe_status = 0, finished = 0;
for (int attempt = 0; attempt < (gl_camera_probe ? 200 : 100); attempt++) {
if (waitpid(probe, &probe_status, WNOHANG) == probe) {
finished = 1;
break;
}
usleep(100000);
}
if (!finished) {
kill(probe, SIGKILL);
waitpid(probe, &probe_status, 0);
printf("Camera%s probe timed out after %d seconds\n",
gl_camera_probe ? " EGL/GLConsumer" : "1", gl_camera_probe ? 20 : 10);
} else {
printf("CameraService probe exit: %d\n",
WIFEXITED(probe_status) ? WEXITSTATUS(probe_status) : -1);
}
}
if (permission_bridge > 0) {
kill(permission_bridge, SIGTERM);
waitpid(permission_bridge, NULL, 0);
permission_bridge = -1;
}
}
int preview_dump_probe = !access(BASE "/qcamera-preview-dump-once", F_OK);
if (!access(BASE "/qcamera-probe-once", F_OK) || preview_dump_probe) {
unlink(BASE "/qcamera-probe-once");
@ -393,6 +480,47 @@ int main(int argc, char **argv) {
if (prop > 0) waitpid(prop, NULL, 0);
if (!umount(dump_path)) rmdir(dump_path);
}
camera_broker_active = !access(BASE "/camera-broker-once",F_OK);
if (camera_broker_active) {
unlink(BASE "/camera-broker-once");
if (mkdir(BASE "/camera-ipc",0777) && errno!=EEXIST) perror("camera IPC directory");
chmod(BASE "/camera-ipc",0777);
unlink(BASE "/camera-ipc/camera.sock");
permission_bridge=fork();
if(!permission_bridge) {
int output=open(BASE "/camera-permission-bridge.log",O_CREAT|O_TRUNC|O_WRONLY,0644);
if(output>=0){fchmod(output,0644);dup2(output,STDOUT_FILENO);dup2(output,STDERR_FILENO);close(output);}
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
execl("/data/local/tmp/vulpes-camera-permission-bridge","vulpes-camera-permission-bridge",NULL);
_exit(127);
}
if(permission_bridge>0) {
int ready=0;
for(int i=0;i<50;i++) {
FILE *log=fopen(BASE "/camera-permission-bridge.log","r");
if(log){char line[256];while(fgets(line,sizeof(line),log))if(strstr(line,"permission bridge ready"))ready=1;fclose(log);}
if(ready)break;
usleep(100000);
}
}
camera_broker=fork();
if(!camera_broker) {
int output=open(BASE "/camera-broker.log",O_CREAT|O_TRUNC|O_WRONLY,0644);
if(output>=0){fchmod(output,0644);dup2(output,STDOUT_FILENO);dup2(output,STDERR_FILENO);close(output);}
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
execl("/data/local/tmp/vulpes-beatles-camera-broker","vulpes-beatles-camera-broker",NULL);
_exit(127);
}
if(camera_broker>0) {
int ready=0;
for(int i=0;i<50;i++) {
struct stat info;
if(!stat(BASE "/camera-ipc/camera.sock",&info) && S_ISSOCK(info.st_mode)){ready=1;break;}
usleep(100000);
}
if(!ready){fprintf(stderr,"Camera broker did not create its socket\n");camera_broker_active=0;}
}
}
if (seconds >= 180) {
radio = fork();
if (!radio) radio_inside();
@ -450,6 +578,18 @@ int main(int argc, char **argv) {
}
if(radio>0) {kill(-radio,SIGKILL);waitpid(radio,NULL,0);}
}
if (camera_broker > 0) {
kill(-camera_broker,SIGTERM);
for(int i=0;i<30;i++) {
if(waitpid(camera_broker,NULL,WNOHANG)==camera_broker){camera_broker=-1;break;}
usleep(100000);
}
if(camera_broker>0){kill(-camera_broker,SIGKILL);waitpid(camera_broker,NULL,0);}
}
if (permission_bridge > 0) {
kill(permission_bridge, SIGTERM);
waitpid(permission_bridge, NULL, 0);
}
clean_runtime();
lid_restore(&lid);
if (ioctl(fb,FBIOPUT_VSCREENINFO,&original)) perror("restore framebuffer mode");