1/* Copyright (C) 2010 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12
13/*
14 * Contains the Core-side implementation of the "core-ui-control" service that is
15 * part of the UI control protocol. Here we send UI control commands to the UI.
16 */
17
18#include "android/android.h"
19#include "android/hw-control.h"
20#include "android/looper.h"
21#include "android/async-utils.h"
22#include "android/sync-utils.h"
23#include "android/utils/debug.h"
24#include "android/protocol/ui-commands.h"
25#include "android/protocol/ui-commands-proxy.h"
26#include "android/protocol/ui-commands-api.h"
27
28/* Descriptor for the UI commands proxy. */
29typedef struct UICmdProxy {
30    /* I/O associated with this descriptor. */
31    LoopIo          io;
32
33    /* Looper associated with this descriptor. */
34    Looper*         looper;
35
36    /* Writer to send UI commands. */
37    SyncSocket*     sync_writer;
38
39    /* Socket descriptor for this service. */
40    int             sock;
41} UICmdProxy;
42
43/* One and only one UICmdProxy instance. */
44static UICmdProxy    _uiCmdProxy;
45
46/* Implemented in android/console.c */
47extern void destroy_uicmd_client(void);
48
49/* Calculates timeout for transferring the given number of bytes via socket.
50 * Return:
51 *  Number of milliseconds during which the entire number of bytes is expected
52 *  to be transferred via socket.
53 */
54static int
55_uiCmdProxy_get_timeout(size_t data_size)
56{
57    // Min 2 seconds + 10 millisec for each transferring byte.
58    // TODO: Come up with a better arithmetics here.
59    return 2000 + data_size * 10;
60}
61
62/* Sends request to the UI client of this service.
63 * Param:
64 *  cmd_type, cmd_param, cmd_param_size - Define the command to send.
65 * Return:
66 *  0 on success, or < 0 on failure.
67 */
68static int
69_uiCmdProxy_send_command(uint8_t cmd_type,
70                         void* cmd_param,
71                         uint32_t cmd_param_size)
72{
73    UICmdHeader header;
74    int status = syncsocket_start_write(_uiCmdProxy.sync_writer);
75    if (!status) {
76        // Initialize and send the header.
77        header.cmd_type = cmd_type;
78        header.cmd_param_size = cmd_param_size;
79        status = syncsocket_write(_uiCmdProxy.sync_writer, &header, sizeof(header),
80                                  _uiCmdProxy_get_timeout(sizeof(header)));
81        // If there are command parameters, send them too.
82        if (status > 0 && cmd_param != NULL && cmd_param_size > 0) {
83            status = syncsocket_write(_uiCmdProxy.sync_writer, cmd_param,
84                                      cmd_param_size,
85                                      _uiCmdProxy_get_timeout(cmd_param_size));
86        }
87        status = syncsocket_result(status);
88        syncsocket_stop_write(_uiCmdProxy.sync_writer);
89    }
90    if (status < 0) {
91        derror("Send UI command %d (%u bytes) has failed: %s\n",
92               cmd_type, cmd_param_size, errno_str);
93    }
94    return status;
95}
96
97/* Asynchronous I/O callback for UICmdProxy instance.
98 * We expect this callback to be called only on UI detachment condition. In this
99 * case the event should be LOOP_IO_READ, and read should fail with errno set
100 * to ECONNRESET.
101 * Param:
102 *  opaque - UICmdProxy instance.
103 */
104static void
105_uiCmdProxy_io_func(void* opaque, int fd, unsigned events)
106{
107    UICmdProxy* uicmd = (UICmdProxy*)opaque;
108    AsyncReader reader;
109    AsyncStatus status;
110    uint8_t read_buf[1];
111
112    if (events & LOOP_IO_WRITE) {
113        derror("Unexpected LOOP_IO_WRITE in _uiCmdProxy_io_func.\n");
114        return;
115    }
116
117    // Try to read
118    asyncReader_init(&reader, read_buf, sizeof(read_buf), &uicmd->io);
119    status = asyncReader_read(&reader);
120    // We expect only error status here.
121    if (status != ASYNC_ERROR) {
122        derror("Unexpected read status %d in _uiCmdProxy_io_func\n", status);
123        return;
124    }
125    // We expect only socket disconnection error here.
126    if (errno != ECONNRESET) {
127        derror("Unexpected read error %d (%s) in _uiCmdProxy_io_func.\n",
128               errno, errno_str);
129        return;
130    }
131
132    // Client got disconnectted.
133    destroy_uicmd_client();
134}
135/* a callback function called when the system wants to change the brightness
136 * of a given light. 'light' is a string which can be one of:
137 * 'lcd_backlight', 'button_backlight' or 'Keyboard_backlight'
138 *
139 * brightness is an integer (acceptable range are 0..255), however the
140 * default is around 105, and we probably don't want to dim the emulator's
141 * output at that level.
142 */
143static void
144_uiCmdProxy_brightness_change_callback(void* opaque,
145                                       const char* light,
146                                       int brightness)
147{
148    // Calculate size of the command parameters.
149    const size_t cmd_size = sizeof(UICmdChangeDispBrightness) + strlen(light) + 1;
150    // Allocate and initialize parameters.
151    UICmdChangeDispBrightness* cmd =
152        (UICmdChangeDispBrightness*)qemu_malloc(cmd_size);
153    cmd->brightness = brightness;
154    strcpy(cmd->light, light);
155    // Send the command.
156    _uiCmdProxy_send_command(AUICMD_CHANGE_DISP_BRIGHTNESS, cmd, cmd_size);
157    qemu_free(cmd);
158}
159
160int
161uiCmdProxy_create(int fd)
162{
163    // Initialize the only UICmdProxy instance.
164    _uiCmdProxy.sock = fd;
165    _uiCmdProxy.looper = looper_newCore();
166    loopIo_init(&_uiCmdProxy.io, _uiCmdProxy.looper, _uiCmdProxy.sock,
167                _uiCmdProxy_io_func, &_uiCmdProxy);
168    loopIo_wantRead(&_uiCmdProxy.io);
169    _uiCmdProxy.sync_writer = syncsocket_init(fd);
170    if (_uiCmdProxy.sync_writer == NULL) {
171        derror("Unable to initialize UICmdProxy writer: %s\n", errno_str);
172        uiCmdProxy_destroy();
173        return -1;
174    }
175    {
176        // Set brighness change callback, so we can notify
177        // the UI about the event.
178        AndroidHwControlFuncs  funcs;
179        funcs.light_brightness = _uiCmdProxy_brightness_change_callback;
180        android_hw_control_set(&_uiCmdProxy, &funcs);
181    }
182    return 0;
183}
184
185void
186uiCmdProxy_destroy()
187{
188    // Destroy the sync writer.
189    if (_uiCmdProxy.sync_writer != NULL) {
190        syncsocket_close(_uiCmdProxy.sync_writer);
191        syncsocket_free(_uiCmdProxy.sync_writer);
192    }
193    if (_uiCmdProxy.looper != NULL) {
194        // Stop all I/O that may still be going on.
195        loopIo_done(&_uiCmdProxy.io);
196        looper_free(_uiCmdProxy.looper);
197        _uiCmdProxy.looper = NULL;
198    }
199    _uiCmdProxy.sock = -1;
200}
201
202int
203uicmd_set_window_scale(double scale, int is_dpi)
204{
205    UICmdSetWindowsScale cmd;
206    cmd.scale = scale;
207    cmd.is_dpi = is_dpi;
208    return _uiCmdProxy_send_command(AUICMD_SET_WINDOWS_SCALE, &cmd, sizeof(cmd));
209}
210