Merge lp:~charlesk/indicator-display/adb-key-auth into lp:indicator-display/15.10

Proposed by Charles Kerr
Status: Merged
Approved by: Xavi Garcia
Approved revision: 42
Merged at revision: 18
Proposed branch: lp:~charlesk/indicator-display/adb-key-auth
Merge into: lp:indicator-display/15.10
Diff against target: 3348 lines (+2613/-310)
40 files modified
.bzr-builddeb/default.conf (+0/-2)
CMakeLists.txt (+76/-60)
cmake/FindGMock.cmake (+0/-10)
cmake/GCov.cmake (+0/-51)
cmake/GdbusCodegen.cmake (+0/-36)
debian/control (+6/-0)
po/POTFILES.in (+1/-0)
src/CMakeLists.txt (+36/-28)
src/adbd-client.cpp (+303/-0)
src/adbd-client.h (+74/-0)
src/dbus-names.h (+60/-0)
src/greeter.cpp (+161/-0)
src/greeter.h (+47/-0)
src/indicator.cpp (+37/-0)
src/indicator.h (+6/-7)
src/main.cpp (+13/-0)
src/rotation-lock.cpp (+1/-0)
src/usb-manager.cpp (+180/-0)
src/usb-manager.h (+48/-0)
src/usb-monitor.cpp (+81/-0)
src/usb-monitor.h (+52/-0)
src/usb-snap.cpp (+250/-0)
src/usb-snap.h (+42/-0)
tests/CMakeLists.txt (+33/-33)
tests/integration/CMakeLists.txt (+24/-0)
tests/integration/usb-manager-test.cpp (+226/-0)
tests/unit/CMakeLists.txt (+34/-0)
tests/unit/adbd-client-test.cpp (+95/-0)
tests/unit/rotation-lock-test.cpp (+3/-3)
tests/unit/usb-snap-test.cpp (+143/-0)
tests/utils/CMakeLists.txt (+17/-0)
tests/utils/adbd-server.h (+150/-0)
tests/utils/dbus-types.h (+42/-0)
tests/utils/glib-fixture.h (+115/-64)
tests/utils/gtest-qt-print-helpers.h (+45/-0)
tests/utils/mock-greeter.h (+32/-0)
tests/utils/mock-usb-monitor.h (+32/-0)
tests/utils/qmain.cpp (+60/-0)
tests/utils/qt-fixture.h (+74/-0)
tests/utils/test-dbus-fixture.h (+14/-16)
To merge this branch: bzr merge lp:~charlesk/indicator-display/adb-key-auth
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
Matthew Paul Thomas (community) design Needs Fixing
Xavi Garcia Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+289126@code.launchpad.net

Commit message

When a new device appears to ADB, prompt the user whether or not to allow the connection.

Description of the change

When a new device appears to ADB, prompt the user whether or not to allow the connection.

Reference java implementation @ http://androidxref.com/5.1.1_r6/xref/frameworks/base/services/usb/java/com/android/server/usb/UsbDebuggingManager.java#59

Basically, the ADBD daemon talks to the indicator via a local domain socket. ADBD sends a request string "PK" + the public key. The indicator's job is to decode the public key and present the resulting fingerprint to the user to allow or deny. Reference Android prompt: http://i.stack.imgur.com/MPh73.png

The indicator returns the strings 'OK' or 'NO' back down the socket depending on the user's choice. If the user clicked the 'remember this' checkbox, we'll also append the public key on its own new line to the textfile "/data/misc/adb/adb_keys".

NB 1: unity-notifications doesn't yet have a checkbox action, so this is omitted from this MP. To honor the most common use case first, this first cut remembers the public key if the user allows the connection.

NB 2: this code is currently being used for testing. The single line in main.cpp 'g_setenv("G_MESSAGES_DEBUG", "all", true);' is to enable verbose logging in ~/.cache/upstart/indicator-display.log and will be removed before landing.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
38. By Charles Kerr

Remove the minimum version of 0.4 for libqtdbusmock1-dev, it's causing a failure on Jenkins wily and was only included due to copy-paste from indicator-network. So let's see how Wily goes without it.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
39. By Charles Kerr

use cmake-extras' EnableCoverageReport instead of home-rolled rules

40. By Charles Kerr

in POTFILES.in, fix copy-paste typo

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
41. By Charles Kerr

in usb-manager.cpp, remove the remember_choice workaround since is already working around it

42. By Charles Kerr

tweak class description comments

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Looks good to me...

Just a minor comment that don't prevent this to be approved IMHO:

Would it make sense to have qDBusArgumentToMap and WAIT_FOR_SIGNALS defined in a common place?
I see both are defined twice and the code is the same...
In fact this is something I'm using in my tests and that maybe would be good to have in a common test-utils library or something... (maybe something to be done in the next future)

Revision history for this message
Xavi Garcia (xavi-garcia-mena) :
review: Approve
43. By Charles Kerr

add tests/utils/qdbus-helpers.h so that we only define qDBusArgumentToMap() in one place

44. By Charles Kerr

de-dupe use of dbus names

45. By Charles Kerr

introduce a QtFixture gtest base class to reduce redundancy in test fixtures' helper/util code

46. By Charles Kerr

sync with lp:~ubuntu-branches/ubuntu/wily/indicator-display/wily/ to pick up 0.1+15.10.20150727-0ubuntu2~gcc5.1

47. By Charles Kerr

fix warning message

48. By Charles Kerr

sync with trunk

49. By Charles Kerr

re-add .bzr-builddeb, which got removed by accident

50. By Charles Kerr

add some extra debug statements to usb-manager.cpp to track user response and the act of writing the pk out to disk

Revision history for this message
Matthew Paul Thomas (mpt) wrote :

Please change “Deny” to “Don’t Allow” for consistency with other permission prompts.
<https://wiki.ubuntu.com/SecurityPermissions#permission-prompt>

review: Needs Fixing (design)
51. By Charles Kerr

turn off verbose debugging

52. By Charles Kerr

if our USB device is disconnected while prompting the user for ADBD, cancel the prompt.

53. By Charles Kerr

in UsbManager, reset AdbdClient on usb disconnect

54. By Charles Kerr

add Greeter skeleton

55. By Charles Kerr

get greeter's IsActive property working

56. By Charles Kerr

fix typo

57. By Charles Kerr

make wait_for_signals() a macro again so that the GTest log messages will give the test file and line number

58. By Charles Kerr

in the mock ADB server, keep making a request until a response is received.

59. By Charles Kerr

add multiple usb requests + disconnects to confirm the notification appears on subsequent requests

60. By Charles Kerr

don't show the snap decision until we're out of the greeter

61. By Charles Kerr

add tests for not showing snap decisions in greeter mode

62. By Charles Kerr

replace text 'Deny' with 'Don't Allow' for consistency with other permission prompts

63. By Charles Kerr

keep the adbd socket open even when the lockscreen is closed. hold the pkrequest state in USBManager until the screen's unlocked.

64. By Charles Kerr

fix missing field initialization compiler warning

65. By Charles Kerr

fix UsbManager dtor issue found by valgrind

Revision history for this message
Charles Kerr (charlesk) wrote :

re-approving up to r65 from ondra revie

review: Approve
66. By Charles Kerr

add tracer g_debug() calls for the benefit of the integration tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.bzr-builddeb'
2=== removed directory '.bzr-builddeb'
3=== added file '.bzr-builddeb/default.conf'
4--- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
5+++ .bzr-builddeb/default.conf 2016-03-24 16:01:39 +0000
6@@ -0,0 +1,2 @@
7+[BUILDDEB]
8+split = True
9
10=== removed file '.bzr-builddeb/default.conf'
11--- .bzr-builddeb/default.conf 2014-09-05 20:21:31 +0000
12+++ .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
13@@ -1,2 +0,0 @@
14-[BUILDDEB]
15-split = True
16
17=== modified file 'CMakeLists.txt'
18--- CMakeLists.txt 2014-08-21 03:56:45 +0000
19+++ CMakeLists.txt 2016-03-24 16:01:39 +0000
20@@ -1,72 +1,88 @@
21-project (indicator-display)
22-cmake_minimum_required (VERSION 2.8.9)
23-
24-list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
25-
26-set (PROJECT_VERSION "14.10.0")
27-set (PACKAGE ${CMAKE_PROJECT_NAME})
28-set (GETTEXT_PACKAGE indicator-display)
29-add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}"
30- -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}")
31-
32-set (SERVICE_LIB ${PACKAGE})
33-set (SERVICE_EXEC "${PACKAGE}-service")
34-
35-option (enable_tests "Build the package's automatic tests." ON)
36-option (enable_lcov "Generate lcov code coverage reports." ON)
37+project(indicator-display LANGUAGES C CXX)
38+cmake_minimum_required(VERSION 2.8.9)
39+
40+list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
41+
42+set(PACKAGE ${CMAKE_PROJECT_NAME})
43+set(GETTEXT_PACKAGE indicator-display)
44+add_definitions(
45+ -DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}"
46+ -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}"
47+)
48+
49+set(SERVICE_LIB ${PACKAGE})
50+set(SERVICE_EXEC "${PACKAGE}-service")
51+
52+option(enable_tests "Build the package's automatic tests." ON)
53+option(enable_coverage "Generate code coverage reports." ON)
54
55 ##
56 ## GNU standard paths
57 ##
58-include (GNUInstallDirs)
59-if (EXISTS "/etc/debian_version") # Workaround for libexecdir on debian
60- set (CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}")
61- set (CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}")
62-endif ()
63-set (CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}")
64-set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}")
65+
66+include(GNUInstallDirs)
67+if(EXISTS "/etc/debian_version") # Workaround for libexecdir on debian
68+ set(CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}")
69+ set(CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}")
70+endif()
71+set(CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}")
72+set(CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}")
73
74 ##
75 ## Check for prerequisites
76 ##
77
78-find_package (PkgConfig REQUIRED)
79-
80-include (FindPkgConfig)
81-pkg_check_modules (SERVICE_DEPS REQUIRED
82- gio-unix-2.0>=2.36
83- glib-2.0>=2.36)
84-include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
85-
86-##
87-##
88-##
89-
90-set (CMAKE_INCLUDE_CURRENT_DIR OFF)
91-include_directories (${CMAKE_CURRENT_SOURCE_DIR})
92+# threads...
93+set(THREADS_PREFER_PTHREAD_FLAG ON)
94+find_package(Threads REQUIRED)
95+if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 3.1)
96+ set(THREAD_LINK_LIBRARIES -pthread)
97+else()
98+ set(THREAD_LINK_LIBRARIES Threads::Threads) # introduced in cmake 3.1
99+endif()
100+
101+find_package(PkgConfig REQUIRED)
102+
103+# glib...
104+set(GLIB_MINIMUM 2.36)
105+pkg_check_modules(SERVICE_DEPS REQUIRED
106+ gio-unix-2.0>=${GLIB_MINIMUM}
107+ glib-2.0>=${GLIB_MINIMUM}
108+ gudev-1.0
109+)
110+include_directories (SYSTEM
111+ ${SERVICE_DEPS_INCLUDE_DIRS}
112+)
113+
114+##
115+## Compiler settings
116+##
117+
118+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
119
120 # set the compiler warnings
121-if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
122- set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Weverything -Wno-c++98-compat")
123-else()
124- set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wall -Wextra -Wpedantic")
125-endif()
126-set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-missing-field-initializers") # GActionEntry
127-
128-# testing & coverage
129-if (${enable_tests})
130- set (GTEST_SOURCE_DIR /usr/src/gtest/src)
131- set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..)
132- set (GTEST_LIBS -lpthread)
133- enable_testing ()
134- if (${enable_lcov})
135- include(GCov)
136- endif ()
137-endif ()
138-
139-add_subdirectory (src)
140-add_subdirectory (data)
141-add_subdirectory (po)
142-if (${enable_tests})
143- add_subdirectory (tests)
144+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
145+ list(APPEND CXX_WARNING_ARGS -Weverything -Wno-c++98-compat -Wno-padded)
146+elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
147+ list(APPEND CXX_WARNING_ARGS -Wall -Wextra -Wpedantic)
148+endif()
149+
150+add_compile_options(-std=c++14 -fPIC -g)
151+
152+##
153+## Testing & Coverage
154+##
155+
156+if(${enable_tests})
157+ enable_testing()
158+ if(${enable_coverage})
159+ include(EnableCoverageReport)
160+ endif()
161+endif()
162+
163+add_subdirectory(src)
164+add_subdirectory(data)
165+add_subdirectory(po)
166+if (${enable_tests})
167+ add_subdirectory(tests)
168 endif ()
169
170=== removed file 'cmake/FindGMock.cmake'
171--- cmake/FindGMock.cmake 2014-08-21 04:03:20 +0000
172+++ cmake/FindGMock.cmake 1970-01-01 00:00:00 +0000
173@@ -1,10 +0,0 @@
174-# Build with system gmock and embedded gtest
175-set (GMOCK_INCLUDE_DIRS "/usr/include/gmock/include" CACHE PATH "gmock source include directory")
176-set (GMOCK_SOURCE_DIR "/usr/src/gmock" CACHE PATH "gmock source directory")
177-set (GTEST_INCLUDE_DIRS "${GMOCK_SOURCE_DIR}/gtest/include" CACHE PATH "gtest source include directory")
178-
179-add_subdirectory(${GMOCK_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gmock")
180-
181-set(GTEST_LIBRARIES gtest)
182-set(GTEST_MAIN_LIBRARIES gtest_main)
183-set(GMOCK_LIBRARIES gmock gmock_main)
184
185=== removed file 'cmake/GCov.cmake'
186--- cmake/GCov.cmake 2014-08-21 04:03:20 +0000
187+++ cmake/GCov.cmake 1970-01-01 00:00:00 +0000
188@@ -1,51 +0,0 @@
189-if (CMAKE_BUILD_TYPE MATCHES coverage)
190- set(GCOV_FLAGS "${GCOV_FLAGS} --coverage")
191- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCOV_FLAGS}")
192- set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${GCOV_FLAGS}")
193- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCOV_FLAGS}")
194- set(GCOV_LIBS ${GCOV_LIBS} gcov)
195-
196- find_program(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin")
197- if (NOT GCOVR_EXECUTABLE)
198- message(STATUS "Gcovr binary was not found, can not generate XML coverage info.")
199- else ()
200- message(STATUS "Gcovr found, can generate XML coverage info.")
201- add_custom_target (coverage-xml
202- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
203- COMMAND "${GCOVR_EXECUTABLE}" --exclude="test.*" -x -r "${CMAKE_SOURCE_DIR}"
204- --object-directory=${CMAKE_BINARY_DIR} -o coverage.xml)
205- endif()
206-
207- find_program(LCOV_EXECUTABLE lcov HINTS ${LCOV_ROOT} "${GCOVR_ROOT}/bin")
208- find_program(GENHTML_EXECUTABLE genhtml HINTS ${GENHTML_ROOT})
209- if (NOT LCOV_EXECUTABLE)
210- message(STATUS "Lcov binary was not found, can not generate HTML coverage info.")
211- else ()
212- if(NOT GENHTML_EXECUTABLE)
213- message(STATUS "Genthml binary not found, can not generate HTML coverage info.")
214- else()
215- message(STATUS "Lcov and genhtml found, can generate HTML coverage info.")
216- add_custom_target (coverage-html
217- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
218- COMMAND "${CMAKE_CTEST_COMMAND}" --force-new-ctest-process --verbose
219- COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture | ${CMAKE_SOURCE_DIR}/trim-lcov.py > dconf-lcov.info
220- COMMAND "${LCOV_EXECUTABLE}" -r dconf-lcov.info /usr/include/\\* -o nosys-lcov.info
221- COMMAND LANG=C "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory lcov-html --legend --show-details nosys-lcov.info
222- COMMAND ${CMAKE_COMMAND} -E echo ""
223- COMMAND ${CMAKE_COMMAND} -E echo "file://${CMAKE_BINARY_DIR}/lcov-html/index.html"
224- COMMAND ${CMAKE_COMMAND} -E echo "")
225- #COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture --output-file coverage.info --no-checksum
226- #COMMAND "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info
227- #COMMAND ${CMAKE_COMMAND} -E echo "\\#define foo \\\"bar\\\""
228- #)
229- endif()
230- endif()
231-endif()
232-
233-
234- #$(MAKE) $(AM_MAKEFLAGS) check
235- #lcov --directory $(top_builddir) --capture --test-name dconf | $(top_srcdir)/trim-lcov.py > dconf-lcov.info
236- #LANG=C genhtml --prefix $(top_builddir) --output-directory lcov-html --legend --show-details dconf-lcov.info
237- #@echo
238- #@echo " file://$(abs_top_builddir)/lcov-html/index.html"
239- #@echo
240
241=== removed file 'cmake/GdbusCodegen.cmake'
242--- cmake/GdbusCodegen.cmake 2014-08-21 04:03:20 +0000
243+++ cmake/GdbusCodegen.cmake 1970-01-01 00:00:00 +0000
244@@ -1,36 +0,0 @@
245-cmake_minimum_required(VERSION 2.6)
246-if(POLICY CMP0011)
247- cmake_policy(SET CMP0011 NEW)
248-endif(POLICY CMP0011)
249-
250-find_program(GDBUS_CODEGEN NAMES gdbus-codegen DOC "gdbus-codegen executable")
251-if(NOT GDBUS_CODEGEN)
252- message(FATAL_ERROR "Excutable gdbus-codegen not found")
253-endif()
254-
255-macro(add_gdbus_codegen outfiles name prefix service_xml)
256- add_custom_command(
257- OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
258- COMMAND "${GDBUS_CODEGEN}"
259- --interface-prefix "${prefix}"
260- --generate-c-code "${name}"
261- "${service_xml}"
262- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
263- DEPENDS ${ARGN} "${service_xml}"
264- )
265- list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c")
266-endmacro(add_gdbus_codegen)
267-
268-macro(add_gdbus_codegen_with_namespace outfiles name prefix namespace service_xml)
269- add_custom_command(
270- OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
271- COMMAND "${GDBUS_CODEGEN}"
272- --interface-prefix "${prefix}"
273- --generate-c-code "${name}"
274- --c-namespace "${namespace}"
275- "${service_xml}"
276- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
277- DEPENDS ${ARGN} "${service_xml}"
278- )
279- list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c")
280-endmacro(add_gdbus_codegen_with_namespace)
281
282=== modified file 'debian/control'
283--- debian/control 2016-03-17 18:48:26 +0000
284+++ debian/control 2016-03-24 16:01:39 +0000
285@@ -4,12 +4,18 @@
286 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
287 XSBC-Original-Maintainer: Charles Kerr <charles.kerr@canonical.com>
288 Build-Depends: cmake,
289+ cmake-extras (>= 0.4),
290 dbus,
291 libglib2.0-dev (>= 2.36),
292+ libgudev-1.0-dev,
293 libproperties-cpp-dev,
294 # for coverage reports
295 lcov,
296 # for tests
297+ qt5-default,
298+ qtbase5-dev,
299+ libqtdbusmock1-dev,
300+ libqtdbustest1-dev,
301 cppcheck,
302 libgtest-dev,
303 google-mock (>= 1.6.0+svn437),
304
305=== modified file 'po/POTFILES.in'
306--- po/POTFILES.in 2014-08-20 03:13:45 +0000
307+++ po/POTFILES.in 2016-03-24 16:01:39 +0000
308@@ -1,1 +1,2 @@
309 src/rotation-lock.cpp
310+src/usb-snap.cpp
311
312=== modified file 'src/CMakeLists.txt'
313--- src/CMakeLists.txt 2014-08-21 03:35:16 +0000
314+++ src/CMakeLists.txt 2016-03-24 16:01:39 +0000
315@@ -1,29 +1,37 @@
316-set (SERVICE_LIB "indicatordisplayservice")
317-set (SERVICE_EXEC "indicator-display-service")
318-
319-add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}")
320-
321-# handwritten source code...
322-set (SERVICE_LIB_HANDWRITTEN_SOURCES
323- exporter.cpp
324- rotation-lock.cpp)
325-
326-add_library (${SERVICE_LIB} STATIC
327- ${SERVICE_LIB_HANDWRITTEN_SOURCES})
328-
329-# add the bin dir to the include path so that
330-# the compiler can find the generated header files
331-include_directories (${CMAKE_CURRENT_BINARY_DIR})
332-
333-link_directories (${SERVICE_DEPS_LIBRARY_DIRS})
334-
335-set (SERVICE_EXEC_HANDWRITTEN_SOURCES main.cpp)
336-add_executable (${SERVICE_EXEC} ${SERVICE_EXEC_HANDWRITTEN_SOURCES})
337-target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS})
338-install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR})
339-
340-# add warnings/coverage info on handwritten files
341-# but not the generated ones...
342-set_property (SOURCE ${SERVICE_LIB_HANDWRITTEN_SOURCES} ${SERVICE_EXEC_HANDWRITTEN_SOURCES}
343- APPEND_STRING PROPERTY COMPILE_FLAGS " -std=c++11 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}")
344+
345+add_definitions(-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}")
346+
347+add_compile_options(
348+ ${CXX_WARNING_ARGS}
349+)
350+
351+add_library(
352+ ${SERVICE_LIB}
353+ STATIC
354+ adbd-client.cpp
355+ exporter.cpp
356+ greeter.cpp
357+ indicator.cpp
358+ rotation-lock.cpp
359+ usb-manager.cpp
360+ usb-monitor.cpp
361+ usb-snap.cpp
362+)
363+
364+add_executable(
365+ ${SERVICE_EXEC}
366+ main.cpp
367+)
368+
369+target_link_libraries(${SERVICE_EXEC}
370+ ${SERVICE_LIB}
371+ ${SERVICE_DEPS_LIBRARIES}
372+ ${THREAD_LINK_LIBRARIES}
373+)
374+
375+install(
376+ TARGETS
377+ ${SERVICE_EXEC}
378+ RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}
379+)
380
381
382=== added file 'src/adbd-client.cpp'
383--- src/adbd-client.cpp 1970-01-01 00:00:00 +0000
384+++ src/adbd-client.cpp 2016-03-24 16:01:39 +0000
385@@ -0,0 +1,303 @@
386+/*
387+ * Copyright 2016 Canonical Ltd.
388+ *
389+ * This program is free software: you can redistribute it and/or modify it
390+ * under the terms of the GNU General Public License version 3, as published
391+ * by the Free Software Foundation.
392+ *
393+ * This program is distributed in the hope that it will be useful, but
394+ * WITHOUT ANY WARRANTY; without even the implied warranties of
395+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
396+ * PURPOSE. See the GNU General Public License for more details.
397+ *
398+ * You should have received a copy of the GNU General Public License along
399+ * with this program. If not, see <http://www.gnu.org/licenses/>.
400+ *
401+ * Authors:
402+ * Charles Kerr <charles.kerr@canonical.com>
403+ */
404+
405+#include <src/adbd-client.h>
406+
407+#include <gio/gio.h>
408+#include <gio/gunixsocketaddress.h>
409+
410+#include <algorithm>
411+#include <cctype>
412+#include <cstring>
413+#include <chrono>
414+#include <condition_variable>
415+#include <mutex>
416+#include <thread>
417+
418+class GAdbdClient::Impl
419+{
420+public:
421+
422+ explicit Impl(const std::string& socket_path):
423+ m_socket_path{socket_path},
424+ m_cancellable{g_cancellable_new()},
425+ m_worker_thread{&Impl::worker_func, this}
426+ {
427+ }
428+
429+ ~Impl()
430+ {
431+ // tell the worker thread to stop whatever it's doing and exit.
432+ g_debug("%s Client::Impl dtor, cancelling m_cancellable", G_STRLOC);
433+ g_cancellable_cancel(m_cancellable);
434+ m_pkresponse_cv.notify_one();
435+ m_sleep_cv.notify_one();
436+ m_worker_thread.join();
437+ g_clear_object(&m_cancellable);
438+ }
439+
440+ core::Signal<const PKRequest&>& on_pk_request()
441+ {
442+ return m_on_pk_request;
443+ }
444+
445+private:
446+
447+ // struct to carry request info from the worker thread to the GMainContext thread
448+ struct PKIdleData
449+ {
450+ Impl* self = nullptr;
451+ GCancellable* cancellable = nullptr;
452+ const std::string public_key;
453+
454+ PKIdleData(Impl* self_, GCancellable* cancellable_, std::string public_key_):
455+ self(self_),
456+ cancellable(G_CANCELLABLE(g_object_ref(cancellable_))),
457+ public_key(public_key_) {}
458+
459+ ~PKIdleData() {g_clear_object(&cancellable);}
460+
461+ };
462+
463+ void pass_public_key_to_main_thread(const std::string& public_key)
464+ {
465+ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
466+ on_public_key_request_static,
467+ new PKIdleData{this, m_cancellable, public_key},
468+ [](gpointer id){delete static_cast<PKIdleData*>(id);});
469+ }
470+
471+ static gboolean on_public_key_request_static (gpointer gdata) // runs in main thread
472+ {
473+ /* NB: It's possible (though unlikely) that data.self was destroyed
474+ while this callback was pending, so we must check is-cancelled FIRST */
475+ auto data = static_cast<PKIdleData*>(gdata);
476+ if (!g_cancellable_is_cancelled(data->cancellable))
477+ {
478+ // notify our listeners of the request
479+ auto self = data->self;
480+ struct PKRequest req;
481+ req.public_key = data->public_key;
482+ req.fingerprint = get_fingerprint(req.public_key);
483+ req.respond = [self](PKResponse response){self->on_public_key_response(response);};
484+ self->m_on_pk_request(req);
485+ }
486+
487+ return G_SOURCE_REMOVE;
488+ }
489+
490+ void on_public_key_response(PKResponse response)
491+ {
492+ // set m_pkresponse and wake up the waiting worker thread
493+ std::unique_lock<std::mutex> lk(m_pkresponse_mutex);
494+ m_pkresponse = response;
495+ m_pkresponse_ready = true;
496+ m_pkresponse_cv.notify_one();
497+ }
498+
499+ /***
500+ ****
501+ ***/
502+
503+ void worker_func() // runs in worker thread
504+ {
505+ const std::string socket_path {m_socket_path};
506+
507+ while (!g_cancellable_is_cancelled(m_cancellable))
508+ {
509+ g_debug("%s creating a client socket to '%s'", G_STRLOC, socket_path.c_str());
510+ auto socket = create_client_socket(socket_path);
511+ bool got_valid_req = false;
512+
513+ g_debug("%s calling read_request", G_STRLOC);
514+ std::string reqstr;
515+ if (socket != nullptr)
516+ reqstr = read_request(socket);
517+ if (!reqstr.empty())
518+ g_debug("%s got request [%s]", G_STRLOC, reqstr.c_str());
519+
520+ if (reqstr.substr(0,2) == "PK") {
521+ PKResponse response = PKResponse::DENY;
522+ const auto public_key = reqstr.substr(2);
523+ g_debug("%s got pk [%s]", G_STRLOC, public_key.c_str());
524+ if (!public_key.empty()) {
525+ got_valid_req = true;
526+ std::unique_lock<std::mutex> lk(m_pkresponse_mutex);
527+ m_pkresponse_ready = false;
528+ pass_public_key_to_main_thread(public_key);
529+ m_pkresponse_cv.wait(lk, [this](){
530+ return m_pkresponse_ready || g_cancellable_is_cancelled(m_cancellable);
531+ });
532+ response = m_pkresponse;
533+ g_debug("%s got response '%d', is-cancelled %d", G_STRLOC,
534+ int(response),
535+ int(g_cancellable_is_cancelled(m_cancellable)));
536+ }
537+ if (!g_cancellable_is_cancelled(m_cancellable))
538+ send_pk_response(socket, response);
539+ } else if (!reqstr.empty()) {
540+ g_warning("Invalid ADB request: [%s]", reqstr.c_str());
541+ }
542+
543+ g_clear_object(&socket);
544+
545+ // If nothing interesting's happening, sleep a bit.
546+ // (Interval copied from UsbDebuggingManager.java)
547+ static constexpr std::chrono::seconds sleep_interval {std::chrono::seconds(1)};
548+ if (!got_valid_req && !g_cancellable_is_cancelled(m_cancellable)) {
549+ std::unique_lock<std::mutex> lk(m_sleep_mutex);
550+ m_sleep_cv.wait_for(lk, sleep_interval);
551+ }
552+ }
553+ }
554+
555+ // connect to a local domain socket
556+ GSocket* create_client_socket(const std::string& socket_path)
557+ {
558+ GError* error {};
559+ auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX,
560+ G_SOCKET_TYPE_STREAM,
561+ G_SOCKET_PROTOCOL_DEFAULT,
562+ &error);
563+ if (error != nullptr) {
564+ g_warning("Error creating adbd client socket: %s", error->message);
565+ g_clear_error(&error);
566+ g_clear_object(&socket);
567+ return nullptr;
568+ }
569+
570+ auto address = g_unix_socket_address_new(socket_path.c_str());
571+ const auto connected = g_socket_connect(socket, address, m_cancellable, &error);
572+ g_clear_object(&address);
573+ if (!connected) {
574+ g_debug("unable to connect to '%s': %s", socket_path.c_str(), error->message);
575+ g_clear_error(&error);
576+ g_clear_object(&socket);
577+ return nullptr;
578+ }
579+
580+ return socket;
581+ }
582+
583+ std::string read_request(GSocket* socket)
584+ {
585+ char buf[4096] = {};
586+ g_debug("%s calling g_socket_receive()", G_STRLOC);
587+ const auto n_bytes = g_socket_receive (socket, buf, sizeof(buf), m_cancellable, nullptr);
588+ std::string ret;
589+ if (n_bytes > 0)
590+ ret.append(buf, std::string::size_type(n_bytes));
591+ g_debug("%s g_socket_receive got %d bytes: [%s]", G_STRLOC, int(n_bytes), ret.c_str());
592+ return ret;
593+ }
594+
595+ void send_pk_response(GSocket* socket, PKResponse response)
596+ {
597+ std::string response_str;
598+ switch(response) {
599+ case PKResponse::ALLOW: response_str = "OK"; break;
600+ case PKResponse::DENY: response_str = "NO"; break;
601+ }
602+ g_debug("%s sending reply: [%s]", G_STRLOC, response_str.c_str());
603+
604+ GError* error {};
605+ g_socket_send(socket,
606+ response_str.c_str(),
607+ response_str.size(),
608+ m_cancellable,
609+ &error);
610+ if (error != nullptr) {
611+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
612+ g_warning("GAdbdServer: Error accepting socket connection: %s", error->message);
613+ g_clear_error(&error);
614+ }
615+ }
616+
617+ static std::string get_fingerprint(const std::string& public_key)
618+ {
619+ // The first token is base64-encoded data, so cut on the first whitespace
620+ const std::string base64 (
621+ public_key.begin(),
622+ std::find_if(
623+ public_key.begin(), public_key.end(),
624+ [](const std::string::value_type& ch){return std::isspace(ch);}
625+ )
626+ );
627+
628+ gsize digest_len {};
629+ auto digest = g_base64_decode(base64.c_str(), &digest_len);
630+
631+ auto checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, digest, digest_len);
632+ const gsize checksum_len = checksum ? strlen(checksum) : 0;
633+
634+ // insert ':' between character pairs; eg "ff27b5f3" --> "ff:27:b5:f3"
635+ std::string fingerprint;
636+ for (gsize i=0; i<checksum_len; ) {
637+ fingerprint.append(checksum+i, checksum+i+2);
638+ if (i < checksum_len-2)
639+ fingerprint.append(":");
640+ i += 2;
641+ }
642+
643+ g_clear_pointer(&digest, g_free);
644+ g_clear_pointer(&checksum, g_free);
645+ return fingerprint;
646+ }
647+
648+ const std::string m_socket_path;
649+ GCancellable* m_cancellable = nullptr;
650+ std::thread m_worker_thread;
651+ core::Signal<const PKRequest&> m_on_pk_request;
652+
653+ std::mutex m_sleep_mutex;
654+ std::condition_variable m_sleep_cv;
655+
656+ std::mutex m_pkresponse_mutex;
657+ std::condition_variable m_pkresponse_cv;
658+ bool m_pkresponse_ready = false;
659+ PKResponse m_pkresponse = PKResponse::DENY;
660+};
661+
662+/***
663+****
664+***/
665+
666+AdbdClient::~AdbdClient()
667+{
668+}
669+
670+/***
671+****
672+***/
673+
674+GAdbdClient::GAdbdClient(const std::string& socket_path):
675+ impl{new Impl{socket_path}}
676+{
677+}
678+
679+GAdbdClient::~GAdbdClient()
680+{
681+}
682+
683+core::Signal<const AdbdClient::PKRequest&>&
684+GAdbdClient::on_pk_request()
685+{
686+ return impl->on_pk_request();
687+}
688+
689
690=== added file 'src/adbd-client.h'
691--- src/adbd-client.h 1970-01-01 00:00:00 +0000
692+++ src/adbd-client.h 2016-03-24 16:01:39 +0000
693@@ -0,0 +1,74 @@
694+/*
695+ * Copyright 2016 Canonical Ltd.
696+ *
697+ * This program is free software: you can redistribute it and/or modify it
698+ * under the terms of the GNU General Public License version 3, as published
699+ * by the Free Software Foundation.
700+ *
701+ * This program is distributed in the hope that it will be useful, but
702+ * WITHOUT ANY WARRANTY; without even the implied warranties of
703+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
704+ * PURPOSE. See the GNU General Public License for more details.
705+ *
706+ * You should have received a copy of the GNU General Public License along
707+ * with this program. If not, see <http://www.gnu.org/licenses/>.
708+ *
709+ * Authors:
710+ * Charles Kerr <charles.kerr@canonical.com>
711+ */
712+
713+#pragma once
714+
715+#include <functional>
716+#include <memory>
717+#include <string>
718+
719+#include <core/signal.h>
720+
721+/**
722+ * Receives public key requests from ADBD and sends a response back.
723+ *
724+ * AdbClient only provides a receive/respond mechanism. The decision
725+ * of what response gets sent is delegated out to a listener via
726+ * the on_pk_request signal.
727+ *
728+ * The decider should connect to on_pk_request, listen for PKRequests,
729+ * and call the request's `respond' method with the desired response.
730+ */
731+class AdbdClient
732+{
733+public:
734+ virtual ~AdbdClient();
735+
736+ enum class PKResponse { DENY, ALLOW };
737+
738+ struct PKRequest {
739+ std::string public_key;
740+ std::string fingerprint;
741+ std::function<void(PKResponse)> respond;
742+ };
743+
744+ virtual core::Signal<const PKRequest&>& on_pk_request() =0;
745+
746+protected:
747+ AdbdClient() =default;
748+};
749+
750+/**
751+ * An AdbdClient designed to work with GLib's event loop.
752+ *
753+ * The on_pk_request() signal will be called in global GMainContext's thread;
754+ * ie, just like a function passed to g_idle_add() or g_timeout_add().
755+ */
756+class GAdbdClient: public AdbdClient
757+{
758+public:
759+ explicit GAdbdClient(const std::string& socket_path);
760+ ~GAdbdClient();
761+ core::Signal<const PKRequest&>& on_pk_request() override;
762+
763+private:
764+ class Impl;
765+ std::unique_ptr<Impl> impl;
766+};
767+
768
769=== added file 'src/dbus-names.h'
770--- src/dbus-names.h 1970-01-01 00:00:00 +0000
771+++ src/dbus-names.h 2016-03-24 16:01:39 +0000
772@@ -0,0 +1,60 @@
773+/*
774+ * Copyright 2016 Canonical Ltd.
775+ *
776+ * This program is free software: you can redistribute it and/or modify it
777+ * under the terms of the GNU General Public License version 3, as published
778+ * by the Free Software Foundation.
779+ *
780+ * This program is distributed in the hope that it will be useful, but
781+ * WITHOUT ANY WARRANTY; without even the implied warranties of
782+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
783+ * PURPOSE. See the GNU General Public License for more details.
784+ *
785+ * You should have received a copy of the GNU General Public License along
786+ * with this program. If not, see <http://www.gnu.org/licenses/>.
787+ *
788+ * Authors:
789+ * Charles Kerr <charles.kerr@canonical.com>
790+ */
791+
792+#pragma once
793+
794+namespace DBusNames
795+{
796+ namespace Notify
797+ {
798+ static constexpr char const * NAME = "org.freedesktop.Notifications";
799+ static constexpr char const * PATH = "/org/freedesktop/Notifications";
800+ static constexpr char const * INTERFACE = "org.freedesktop.Notifications";
801+
802+ namespace ActionInvoked
803+ {
804+ static constexpr char const * NAME = "ActionInvoked";
805+ }
806+
807+ namespace NotificationClosed
808+ {
809+ static constexpr char const * NAME = "NotificationClosed";
810+ enum Reason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 };
811+ }
812+ }
813+
814+ namespace UnityGreeter
815+ {
816+ static constexpr char const * NAME = "com.canonical.UnityGreeter";
817+ static constexpr char const * PATH = "/";
818+ static constexpr char const * INTERFACE = "com.canonical.UnityGreeter";
819+ }
820+
821+ namespace Properties
822+ {
823+ static constexpr char const * INTERFACE = "org.freedesktop.DBus.Properties";
824+
825+ namespace PropertiesChanged
826+ {
827+ static constexpr char const* NAME = "PropertiesChanged";
828+ static constexpr char const* ARGS_VARIANT_TYPE = "(sa{sv}as)";
829+ }
830+ }
831+}
832+
833
834=== added file 'src/greeter.cpp'
835--- src/greeter.cpp 1970-01-01 00:00:00 +0000
836+++ src/greeter.cpp 2016-03-24 16:01:39 +0000
837@@ -0,0 +1,161 @@
838+/*
839+ * Copyright 2016 Canonical Ltd.
840+ *
841+ * This program is free software: you can redistribute it and/or modify it
842+ * under the terms of the GNU General Public License version 3, as published
843+ * by the Free Software Foundation.
844+ *
845+ * This program is distributed in the hope that it will be useful, but
846+ * WITHOUT ANY WARRANTY; without even the implied warranties of
847+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
848+ * PURPOSE. See the GNU General Public License for more details.
849+ *
850+ * You should have received a copy of the GNU General Public License along
851+ * with this program. If not, see <http://www.gnu.org/licenses/>.
852+ *
853+ * Authors:
854+ * Charles Kerr <charles.kerr@canonical.com>
855+ */
856+
857+#include <src/dbus-names.h>
858+#include <src/greeter.h>
859+
860+#include <gio/gio.h>
861+
862+class UnityGreeter::Impl
863+{
864+public:
865+
866+ Impl():
867+ m_cancellable{g_cancellable_new()}
868+ {
869+ g_bus_get(G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this);
870+ }
871+
872+ ~Impl()
873+ {
874+ g_cancellable_cancel(m_cancellable);
875+ g_clear_object(&m_cancellable);
876+
877+ if (m_subscription_id != 0)
878+ g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id);
879+
880+ g_clear_object(&m_bus);
881+ }
882+
883+ core::Property<bool>& is_active()
884+ {
885+ return m_is_active;
886+ }
887+
888+private:
889+
890+ static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself)
891+ {
892+ GError* error {};
893+ auto bus = g_bus_get_finish (res, &error);
894+ if (error != nullptr) {
895+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
896+ g_warning("UsbSnap: Error getting session bus: %s", error->message);
897+ g_clear_error(&error);
898+ } else {
899+ static_cast<Impl*>(gself)->on_bus_ready(bus);
900+ }
901+ g_clear_object(&bus);
902+ }
903+
904+ void on_bus_ready(GDBusConnection* bus)
905+ {
906+ m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus)));
907+
908+ g_dbus_connection_call(m_bus,
909+ DBusNames::UnityGreeter::NAME,
910+ DBusNames::UnityGreeter::PATH,
911+ DBusNames::Properties::INTERFACE,
912+ "Get",
913+ g_variant_new("(ss)", DBusNames::UnityGreeter::INTERFACE, "IsActive"),
914+ G_VARIANT_TYPE("(v)"),
915+ G_DBUS_CALL_FLAGS_NONE,
916+ -1,
917+ m_cancellable,
918+ on_get_is_active_ready,
919+ this);
920+
921+ m_subscription_id = g_dbus_connection_signal_subscribe(m_bus,
922+ DBusNames::UnityGreeter::NAME,
923+ DBusNames::Properties::INTERFACE,
924+ DBusNames::Properties::PropertiesChanged::NAME,
925+ DBusNames::UnityGreeter::PATH,
926+ DBusNames::UnityGreeter::INTERFACE,
927+ G_DBUS_SIGNAL_FLAGS_NONE,
928+ on_properties_changed_signal,
929+ this,
930+ nullptr);
931+ }
932+
933+ static void on_get_is_active_ready(GObject* source, GAsyncResult* res, gpointer gself)
934+ {
935+ GError* error {};
936+ auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error);
937+ if (error != nullptr) {
938+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
939+ g_warning("UsbSnap: Error getting session bus: %s", error->message);
940+ g_clear_error(&error);
941+ } else {
942+ GVariant* is_active {};
943+ g_variant_get_child(v, 0, "v", &is_active);
944+ static_cast<Impl*>(gself)->m_is_active.set(g_variant_get_boolean(is_active));
945+ g_clear_pointer(&is_active, g_variant_unref);
946+ }
947+ g_clear_pointer(&v, g_variant_unref);
948+ }
949+
950+ static void on_properties_changed_signal(GDBusConnection* /*connection*/,
951+ const gchar* /*sender_name*/,
952+ const gchar* object_path,
953+ const gchar* interface_name,
954+ const gchar* signal_name,
955+ GVariant* parameters,
956+ gpointer gself)
957+ {
958+ g_return_if_fail(!g_strcmp0(object_path, DBusNames::UnityGreeter::PATH));
959+ g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Properties::INTERFACE));
960+ g_return_if_fail(!g_strcmp0(signal_name, DBusNames::Properties::PropertiesChanged::NAME));
961+ g_return_if_fail(g_variant_is_of_type(parameters, G_VARIANT_TYPE(DBusNames::Properties::PropertiesChanged::ARGS_VARIANT_TYPE)));
962+
963+ auto v = g_variant_get_child_value (parameters, 1);
964+ gboolean is_active {};
965+ if (g_variant_lookup(v, "IsActive", "b", &is_active))
966+ {
967+ g_debug("%s is_active changed to %d", G_STRLOC, int(is_active));
968+ static_cast<Impl*>(gself)->m_is_active.set(is_active);
969+ }
970+ g_clear_pointer(&v, g_variant_unref);
971+ }
972+
973+ core::Property<bool> m_is_active;
974+ GCancellable* m_cancellable {};
975+ GDBusConnection* m_bus {};
976+ unsigned int m_subscription_id {};
977+};
978+
979+/***
980+****
981+***/
982+
983+Greeter::Greeter() =default;
984+
985+Greeter::~Greeter() =default;
986+
987+UnityGreeter::UnityGreeter():
988+ impl{new Impl{}}
989+{
990+}
991+
992+UnityGreeter::~UnityGreeter() =default;
993+
994+core::Property<bool>&
995+UnityGreeter::is_active()
996+{
997+ return impl->is_active();
998+}
999
1000=== added file 'src/greeter.h'
1001--- src/greeter.h 1970-01-01 00:00:00 +0000
1002+++ src/greeter.h 2016-03-24 16:01:39 +0000
1003@@ -0,0 +1,47 @@
1004+/*
1005+ * Copyright 2016 Canonical Ltd.
1006+ *
1007+ * This program is free software: you can redistribute it and/or modify it
1008+ * under the terms of the GNU General Public License version 3, as published
1009+ * by the Free Software Foundation.
1010+ *
1011+ * This program is distributed in the hope that it will be useful, but
1012+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1013+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1014+ * PURPOSE. See the GNU General Public License for more details.
1015+ *
1016+ * You should have received a copy of the GNU General Public License along
1017+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1018+ *
1019+ * Authors:
1020+ * Charles Kerr <charles.kerr@canonical.com>
1021+ */
1022+
1023+#pragma once
1024+
1025+#include <core/property.h>
1026+
1027+#include <memory>
1028+#include <string>
1029+
1030+class Greeter
1031+{
1032+public:
1033+ Greeter();
1034+ virtual ~Greeter();
1035+ virtual core::Property<bool>& is_active() =0;
1036+};
1037+
1038+
1039+class UnityGreeter: public Greeter
1040+{
1041+public:
1042+ UnityGreeter();
1043+ virtual ~UnityGreeter();
1044+ core::Property<bool>& is_active() override;
1045+
1046+protected:
1047+ class Impl;
1048+ std::unique_ptr<Impl> impl;
1049+};
1050+
1051
1052=== added file 'src/indicator.cpp'
1053--- src/indicator.cpp 1970-01-01 00:00:00 +0000
1054+++ src/indicator.cpp 2016-03-24 16:01:39 +0000
1055@@ -0,0 +1,37 @@
1056+/*
1057+ * Copyright 2016 Canonical Ltd.
1058+ *
1059+ * This program is free software: you can redistribute it and/or modify it
1060+ * under the terms of the GNU General Public License version 3, as published
1061+ * by the Free Software Foundation.
1062+ *
1063+ * This program is distributed in the hope that it will be useful, but
1064+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1065+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1066+ * PURPOSE. See the GNU General Public License for more details.
1067+ *
1068+ * You should have received a copy of the GNU General Public License along
1069+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1070+ *
1071+ * Authors:
1072+ * Charles Kerr <charles.kerr@canonical.com>
1073+ */
1074+
1075+#include <src/indicator.h>
1076+
1077+Profile::Profile()
1078+{
1079+}
1080+
1081+Profile::~Profile()
1082+{
1083+}
1084+
1085+SimpleProfile::~SimpleProfile()
1086+{
1087+}
1088+
1089+Indicator::~Indicator()
1090+{
1091+}
1092+
1093
1094=== modified file 'src/indicator.h'
1095--- src/indicator.h 2014-10-06 19:54:15 +0000
1096+++ src/indicator.h 2016-03-24 16:01:39 +0000
1097@@ -1,5 +1,5 @@
1098 /*
1099- * Copyright 2014 Canonical Ltd.
1100+ * Copyright 2014-2016 Canonical Ltd.
1101 *
1102 * This program is free software: you can redistribute it and/or modify it
1103 * under the terms of the GNU General Public License version 3, as published
1104@@ -17,8 +17,7 @@
1105 * Charles Kerr <charles.kerr@canonical.com>
1106 */
1107
1108-#ifndef INDICATOR_DISPLAY_INDICATOR_H
1109-#define INDICATOR_DISPLAY_INDICATOR_H
1110+#pragma once
1111
1112 #include <core/property.h>
1113
1114@@ -52,10 +51,10 @@
1115 virtual std::string name() const =0;
1116 virtual const core::Property<Header>& header() const =0;
1117 virtual std::shared_ptr<GMenuModel> menu_model() const =0;
1118- virtual ~Profile() =default;
1119+ virtual ~Profile();
1120
1121 protected:
1122- Profile() =default;
1123+ Profile();
1124 };
1125
1126
1127@@ -63,6 +62,7 @@
1128 {
1129 public:
1130 SimpleProfile(const char* name, const std::shared_ptr<GMenuModel>& menu): m_name(name), m_menu(menu) {}
1131+ virtual ~SimpleProfile();
1132
1133 std::string name() const {return m_name;}
1134 core::Property<Header>& header() {return m_header;}
1135@@ -79,11 +79,10 @@
1136 class Indicator
1137 {
1138 public:
1139- virtual ~Indicator() =default;
1140+ virtual ~Indicator();
1141
1142 virtual const char* name() const =0;
1143 virtual GSimpleActionGroup* action_group() const =0;
1144 virtual std::vector<std::shared_ptr<Profile>> profiles() const =0;
1145 };
1146
1147-#endif
1148
1149=== modified file 'src/main.cpp'
1150--- src/main.cpp 2014-08-21 03:35:16 +0000
1151+++ src/main.cpp 2016-03-24 16:01:39 +0000
1152@@ -20,6 +20,10 @@
1153 #include <src/exporter.h>
1154 #include <src/rotation-lock.h>
1155
1156+#include <src/greeter.h>
1157+#include <src/usb-manager.h>
1158+#include <src/usb-monitor.h>
1159+
1160 #include <glib/gi18n.h> // bindtextdomain()
1161 #include <gio/gio.h>
1162
1163@@ -54,6 +58,15 @@
1164 exporters.push_back(exporter);
1165 }
1166
1167+ // We need the ADBD handler running,
1168+ // even though it doesn't have an indicator component yet
1169+ static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adbd"};
1170+ static constexpr char const * PUBLIC_KEYS_FILENAME {"/data/misc/adb/adb_keys"};
1171+ auto usb_monitor = std::make_shared<GUDevUsbMonitor>();
1172+ auto greeter = std::make_shared<UnityGreeter>();
1173+ UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor, greeter};
1174+
1175+ // let's go!
1176 g_main_loop_run(loop);
1177
1178 // cleanup
1179
1180=== modified file 'src/rotation-lock.cpp'
1181--- src/rotation-lock.cpp 2015-03-01 00:13:47 +0000
1182+++ src/rotation-lock.cpp 2016-03-24 16:01:39 +0000
1183@@ -43,6 +43,7 @@
1184
1185 ~Impl()
1186 {
1187+ g_signal_handlers_disconnect_by_data(m_settings, this);
1188 g_clear_object(&m_action_group);
1189 g_clear_object(&m_settings);
1190 }
1191
1192=== added file 'src/usb-manager.cpp'
1193--- src/usb-manager.cpp 1970-01-01 00:00:00 +0000
1194+++ src/usb-manager.cpp 2016-03-24 16:01:39 +0000
1195@@ -0,0 +1,180 @@
1196+/*
1197+ * Copyright 2016 Canonical Ltd.
1198+ *
1199+ * This program is free software: you can redistribute it and/or modify it
1200+ * under the terms of the GNU General Public License version 3, as published
1201+ * by the Free Software Foundation.
1202+ *
1203+ * This program is distributed in the hope that it will be useful, but
1204+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1205+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1206+ * PURPOSE. See the GNU General Public License for more details.
1207+ *
1208+ * You should have received a copy of the GNU General Public License along
1209+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1210+ *
1211+ * Authors:
1212+ * Charles Kerr <charles.kerr@canonical.com>
1213+ */
1214+
1215+#include <src/adbd-client.h>
1216+#include <src/usb-manager.h>
1217+#include <src/usb-snap.h>
1218+
1219+#include <glib.h>
1220+
1221+#include <sys/types.h>
1222+#include <sys/stat.h>
1223+#include <fcntl.h>
1224+#include <unistd.h>
1225+
1226+#include <set>
1227+
1228+class UsbManager::Impl
1229+{
1230+public:
1231+
1232+ explicit Impl(
1233+ const std::string& socket_path,
1234+ const std::string& public_keys_filename,
1235+ const std::shared_ptr<UsbMonitor>& usb_monitor,
1236+ const std::shared_ptr<Greeter>& greeter
1237+ ):
1238+ m_socket_path{socket_path},
1239+ m_public_keys_filename{public_keys_filename},
1240+ m_usb_monitor{usb_monitor},
1241+ m_greeter{greeter}
1242+ {
1243+ m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) {
1244+ restart();
1245+ });
1246+
1247+ m_greeter->is_active().changed().connect([this](bool /*is_active*/) {
1248+ maybe_snap();
1249+ });
1250+
1251+ restart();
1252+ }
1253+
1254+ ~Impl()
1255+ {
1256+ if (m_restart_idle_tag)
1257+ g_source_remove(m_restart_idle_tag);
1258+
1259+ clear();
1260+ }
1261+
1262+private:
1263+
1264+ void clear()
1265+ {
1266+ // clear out old state
1267+ m_snap_connections.clear();
1268+ m_snap.reset();
1269+ m_req = decltype(m_req)();
1270+ m_adbd_client.reset();
1271+ }
1272+
1273+ void restart()
1274+ {
1275+ clear();
1276+
1277+ // set a new client
1278+ m_adbd_client.reset(new GAdbdClient{m_socket_path});
1279+ m_adbd_client->on_pk_request().connect(
1280+ [this](const AdbdClient::PKRequest& req) {
1281+ g_debug("%s got pk request: %s", G_STRLOC, req.fingerprint.c_str());
1282+ m_req = req;
1283+ maybe_snap();
1284+ }
1285+ );
1286+ }
1287+
1288+ void maybe_snap()
1289+ {
1290+ // don't prompt in the greeter!
1291+ if (!m_req.public_key.empty() && !m_greeter->is_active().get())
1292+ snap();
1293+ }
1294+
1295+ void snap()
1296+ {
1297+ m_snap = std::make_shared<UsbSnap>(m_req.fingerprint);
1298+ m_snap_connections.insert((*m_snap).on_user_response().connect(
1299+ [this](AdbdClient::PKResponse response, bool remember_choice){
1300+ g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice));
1301+ m_req.respond(response);
1302+ if (remember_choice && (response == AdbdClient::PKResponse::ALLOW))
1303+ write_public_key(m_req.public_key);
1304+ m_restart_idle_tag = g_idle_add([](gpointer gself){
1305+ auto self = static_cast<Impl*>(gself);
1306+ self->m_restart_idle_tag = 0;
1307+ self->restart();
1308+ return G_SOURCE_REMOVE;
1309+ }, this);
1310+ }
1311+ ));
1312+ }
1313+
1314+ void write_public_key(const std::string& public_key)
1315+ {
1316+ g_debug("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str());
1317+
1318+ // confirm the directory exists
1319+ auto dirname = g_path_get_dirname(m_public_keys_filename.c_str());
1320+ const auto dir_exists = g_file_test(dirname, G_FILE_TEST_IS_DIR);
1321+ if (!dir_exists)
1322+ g_warning("ADB data directory '%s' does not exist", dirname);
1323+ g_clear_pointer(&dirname, g_free);
1324+ if (!dir_exists)
1325+ return;
1326+
1327+ // open the file in append mode, with user rw and group r permissions
1328+ const auto fd = open(
1329+ m_public_keys_filename.c_str(),
1330+ O_APPEND|O_CREAT|O_WRONLY,
1331+ S_IRUSR|S_IWUSR|S_IRGRP
1332+ );
1333+ if (fd == -1) {
1334+ g_warning("Error opening ADB datafile: %s", g_strerror(errno));
1335+ return;
1336+ }
1337+
1338+ // write the new public key on its own line
1339+ std::string buf {public_key + '\n'};
1340+ if (write(fd, buf.c_str(), buf.size()) == -1)
1341+ g_warning("Error writing ADB datafile: %d %s", errno, g_strerror(errno));
1342+ close(fd);
1343+ }
1344+
1345+ const std::string m_socket_path;
1346+ const std::string m_public_keys_filename;
1347+ const std::shared_ptr<UsbMonitor> m_usb_monitor;
1348+ const std::shared_ptr<Greeter> m_greeter;
1349+
1350+ unsigned int m_restart_idle_tag {};
1351+
1352+ std::shared_ptr<GAdbdClient> m_adbd_client;
1353+ AdbdClient::PKRequest m_req;
1354+ std::shared_ptr<UsbSnap> m_snap;
1355+ std::set<core::ScopedConnection> m_snap_connections;
1356+};
1357+
1358+/***
1359+****
1360+***/
1361+
1362+UsbManager::UsbManager(
1363+ const std::string& socket_path,
1364+ const std::string& public_keys_filename,
1365+ const std::shared_ptr<UsbMonitor>& usb_monitor,
1366+ const std::shared_ptr<Greeter>& greeter
1367+):
1368+ impl{new Impl{socket_path, public_keys_filename, usb_monitor, greeter}}
1369+{
1370+}
1371+
1372+UsbManager::~UsbManager()
1373+{
1374+}
1375+
1376
1377=== added file 'src/usb-manager.h'
1378--- src/usb-manager.h 1970-01-01 00:00:00 +0000
1379+++ src/usb-manager.h 2016-03-24 16:01:39 +0000
1380@@ -0,0 +1,48 @@
1381+/*
1382+ * Copyright 2016 Canonical Ltd.
1383+ *
1384+ * This program is free software: you can redistribute it and/or modify it
1385+ * under the terms of the GNU General Public License version 3, as published
1386+ * by the Free Software Foundation.
1387+ *
1388+ * This program is distributed in the hope that it will be useful, but
1389+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1390+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1391+ * PURPOSE. See the GNU General Public License for more details.
1392+ *
1393+ * You should have received a copy of the GNU General Public License along
1394+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1395+ *
1396+ * Authors:
1397+ * Charles Kerr <charles.kerr@canonical.com>
1398+ */
1399+
1400+#pragma once
1401+
1402+#include <src/greeter.h>
1403+#include <src/usb-monitor.h>
1404+
1405+#include <memory>
1406+#include <string>
1407+
1408+/**
1409+ * Manager class that connects the AdbdClient, UsbSnap, and manages the public key file
1410+ */
1411+class UsbManager
1412+{
1413+public:
1414+
1415+ UsbManager(
1416+ const std::string& socket_path,
1417+ const std::string& public_key_filename,
1418+ const std::shared_ptr<UsbMonitor>&,
1419+ const std::shared_ptr<Greeter>&
1420+ );
1421+
1422+ ~UsbManager();
1423+
1424+protected:
1425+
1426+ class Impl;
1427+ std::unique_ptr<Impl> impl;
1428+};
1429
1430=== added file 'src/usb-monitor.cpp'
1431--- src/usb-monitor.cpp 1970-01-01 00:00:00 +0000
1432+++ src/usb-monitor.cpp 2016-03-24 16:01:39 +0000
1433@@ -0,0 +1,81 @@
1434+/*
1435+ * Copyright 2016 Canonical Ltd.
1436+ *
1437+ * This program is free software: you can redistribute it and/or modify it
1438+ * under the terms of the GNU General Public License version 3, as published
1439+ * by the Free Software Foundation.
1440+ *
1441+ * This program is distributed in the hope that it will be useful, but
1442+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1443+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1444+ * PURPOSE. See the GNU General Public License for more details.
1445+ *
1446+ * You should have received a copy of the GNU General Public License along
1447+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1448+ *
1449+ * Authors:
1450+ * Charles Kerr <charles.kerr@canonical.com>
1451+ */
1452+
1453+#include <src/usb-monitor.h>
1454+
1455+#include <glib.h>
1456+#include <gudev/gudev.h>
1457+
1458+class GUDevUsbMonitor::Impl
1459+{
1460+public:
1461+
1462+ Impl()
1463+ {
1464+ const char* subsystems[] = {"android_usb", nullptr};
1465+ m_udev_client = g_udev_client_new(subsystems);
1466+ g_signal_connect(m_udev_client, "uevent", G_CALLBACK(on_android_usb_event), this);
1467+ }
1468+
1469+ ~Impl()
1470+ {
1471+ g_signal_handlers_disconnect_by_data(m_udev_client, this);
1472+ g_clear_object(&m_udev_client);
1473+ }
1474+
1475+ core::Signal<const std::string&>& on_usb_disconnected()
1476+ {
1477+ return m_on_usb_disconnected;
1478+ }
1479+
1480+private:
1481+
1482+ static void on_android_usb_event(GUdevClient*, gchar* action, GUdevDevice* device, gpointer gself)
1483+ {
1484+ if (!g_strcmp0(action, "change"))
1485+ if (!g_strcmp0(g_udev_device_get_property(device, "USB_STATE"), "DISCONNECTED"))
1486+ static_cast<Impl*>(gself)->m_on_usb_disconnected(g_udev_device_get_name(device));
1487+ }
1488+
1489+ core::Signal<const std::string&> m_on_usb_disconnected;
1490+
1491+ GUdevClient* m_udev_client = nullptr;
1492+};
1493+
1494+/***
1495+****
1496+***/
1497+
1498+UsbMonitor::UsbMonitor() =default;
1499+
1500+UsbMonitor::~UsbMonitor() =default;
1501+
1502+GUDevUsbMonitor::GUDevUsbMonitor():
1503+ impl{new Impl{}}
1504+{
1505+}
1506+
1507+GUDevUsbMonitor::~GUDevUsbMonitor() =default;
1508+
1509+core::Signal<const std::string&>&
1510+GUDevUsbMonitor::on_usb_disconnected()
1511+{
1512+ return impl->on_usb_disconnected();
1513+}
1514+
1515
1516=== added file 'src/usb-monitor.h'
1517--- src/usb-monitor.h 1970-01-01 00:00:00 +0000
1518+++ src/usb-monitor.h 2016-03-24 16:01:39 +0000
1519@@ -0,0 +1,52 @@
1520+/*
1521+ * Copyright 2016 Canonical Ltd.
1522+ *
1523+ * This program is free software: you can redistribute it and/or modify it
1524+ * under the terms of the GNU General Public License version 3, as published
1525+ * by the Free Software Foundation.
1526+ *
1527+ * This program is distributed in the hope that it will be useful, but
1528+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1529+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1530+ * PURPOSE. See the GNU General Public License for more details.
1531+ *
1532+ * You should have received a copy of the GNU General Public License along
1533+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1534+ *
1535+ * Authors:
1536+ * Charles Kerr <charles.kerr@canonical.com>
1537+ */
1538+
1539+#pragma once
1540+
1541+#include <core/signal.h>
1542+
1543+#include <memory>
1544+#include <string>
1545+
1546+/**
1547+ * Simple interface that emits signals on USB device state changes
1548+ */
1549+class UsbMonitor
1550+{
1551+public:
1552+ UsbMonitor();
1553+ virtual ~UsbMonitor();
1554+ virtual core::Signal<const std::string&>& on_usb_disconnected() =0;
1555+};
1556+
1557+/**
1558+ * Simple GUDev wrapper that notifies on android_usb device state changes
1559+ */
1560+class GUDevUsbMonitor: public UsbMonitor
1561+{
1562+public:
1563+ GUDevUsbMonitor();
1564+ virtual ~GUDevUsbMonitor();
1565+ core::Signal<const std::string&>& on_usb_disconnected() override;
1566+
1567+protected:
1568+ class Impl;
1569+ std::unique_ptr<Impl> impl;
1570+};
1571+
1572
1573=== added file 'src/usb-snap.cpp'
1574--- src/usb-snap.cpp 1970-01-01 00:00:00 +0000
1575+++ src/usb-snap.cpp 2016-03-24 16:01:39 +0000
1576@@ -0,0 +1,250 @@
1577+/*
1578+ * Copyright 2016 Canonical Ltd.
1579+ *
1580+ * This program is free software: you can redistribute it and/or modify it
1581+ * under the terms of the GNU General Public License version 3, as published
1582+ * by the Free Software Foundation.
1583+ *
1584+ * This program is distributed in the hope that it will be useful, but
1585+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1586+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1587+ * PURPOSE. See the GNU General Public License for more details.
1588+ *
1589+ * You should have received a copy of the GNU General Public License along
1590+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1591+ *
1592+ * Authors:
1593+ * Charles Kerr <charles.kerr@canonical.com>
1594+ */
1595+
1596+#include <src/dbus-names.h>
1597+#include <src/usb-snap.h>
1598+
1599+#include <glib/gi18n.h>
1600+#include <gio/gio.h>
1601+
1602+/***
1603+****
1604+***/
1605+
1606+class UsbSnap::Impl
1607+{
1608+public:
1609+
1610+ explicit Impl(const std::string& fingerprint):
1611+ m_fingerprint{fingerprint},
1612+ m_cancellable{g_cancellable_new()}
1613+ {
1614+ g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this);
1615+ }
1616+
1617+ ~Impl()
1618+ {
1619+ g_cancellable_cancel(m_cancellable);
1620+ g_clear_object(&m_cancellable);
1621+
1622+ if (m_subscription_id != 0)
1623+ g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id);
1624+
1625+ if (m_notification_id != 0) {
1626+ GError* error {};
1627+ g_dbus_connection_call_sync(m_bus,
1628+ DBusNames::Notify::NAME,
1629+ DBusNames::Notify::PATH,
1630+ DBusNames::Notify::INTERFACE,
1631+ "CloseNotification",
1632+ g_variant_new("(u)", m_notification_id),
1633+ nullptr,
1634+ G_DBUS_CALL_FLAGS_NONE,
1635+ -1,
1636+ nullptr,
1637+ &error);
1638+ if (error != nullptr) {
1639+ g_warning("Error closing notification: %s", error->message);
1640+ g_clear_error(&error);
1641+ }
1642+ }
1643+
1644+ g_clear_object(&m_bus);
1645+ }
1646+
1647+ core::Signal<AdbdClient::PKResponse,bool>& on_user_response()
1648+ {
1649+ return m_on_user_response;
1650+ }
1651+
1652+private:
1653+
1654+ static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself)
1655+ {
1656+ GError* error {};
1657+ auto bus = g_bus_get_finish (res, &error);
1658+ if (error != nullptr) {
1659+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1660+ g_warning("UsbSnap: Error getting session bus: %s", error->message);
1661+ g_clear_error(&error);
1662+ } else {
1663+ static_cast<Impl*>(gself)->on_bus_ready(bus);
1664+ }
1665+ g_clear_object(&bus);
1666+ }
1667+
1668+ void on_bus_ready(GDBusConnection* bus)
1669+ {
1670+ m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus)));
1671+
1672+ m_subscription_id = g_dbus_connection_signal_subscribe(m_bus,
1673+ DBusNames::Notify::NAME,
1674+ DBusNames::Notify::INTERFACE,
1675+ nullptr,
1676+ DBusNames::Notify::PATH,
1677+ nullptr,
1678+ G_DBUS_SIGNAL_FLAGS_NONE,
1679+ on_notification_signal_static,
1680+ this,
1681+ nullptr);
1682+
1683+ auto body = g_strdup_printf(_("The computer's RSA key fingerprint is: %s"), m_fingerprint.c_str());
1684+
1685+ GVariantBuilder actions_builder;
1686+ g_variant_builder_init(&actions_builder, G_VARIANT_TYPE_STRING_ARRAY);
1687+ g_variant_builder_add(&actions_builder, "s", ACTION_ALLOW);
1688+ g_variant_builder_add(&actions_builder, "s", _("Allow"));
1689+ g_variant_builder_add(&actions_builder, "s", ACTION_DENY);
1690+ g_variant_builder_add(&actions_builder, "s", _("Don't Allow"));
1691+
1692+ GVariantBuilder hints_builder;
1693+ g_variant_builder_init(&hints_builder, G_VARIANT_TYPE_VARDICT);
1694+ g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-non-shaped-icon", g_variant_new_string("true"));
1695+ g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-snap-decisions", g_variant_new_string("true"));
1696+ g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-private-affirmative-tint", g_variant_new_string("true"));
1697+
1698+ auto args = g_variant_new("(susssasa{sv}i)",
1699+ "",
1700+ uint32_t(0),
1701+ "computer-symbolic",
1702+ _("Allow USB Debugging?"),
1703+ body,
1704+ &actions_builder,
1705+ &hints_builder,
1706+ -1);
1707+ g_dbus_connection_call(m_bus,
1708+ DBusNames::Notify::NAME,
1709+ DBusNames::Notify::PATH,
1710+ DBusNames::Notify::INTERFACE,
1711+ "Notify",
1712+ args,
1713+ G_VARIANT_TYPE("(u)"),
1714+ G_DBUS_CALL_FLAGS_NONE,
1715+ -1, // timeout
1716+ m_cancellable,
1717+ on_notify_reply_static,
1718+ this);
1719+
1720+ g_clear_pointer(&body, g_free);
1721+ }
1722+
1723+ static void on_notify_reply_static(GObject* obus, GAsyncResult* res, gpointer gself)
1724+ {
1725+ GError* error {};
1726+ auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error);
1727+ if (error != nullptr) {
1728+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1729+ g_warning("UsbSnap: Error calling Notify: %s", error->message);
1730+ g_clear_error(&error);
1731+ } else {
1732+ uint32_t id {};
1733+ g_variant_get(reply, "(u)", &id);
1734+ static_cast<Impl*>(gself)->on_notify_reply(id);
1735+ }
1736+ g_clear_pointer(&reply, g_variant_unref);
1737+ }
1738+
1739+ void on_notify_reply(uint32_t id)
1740+ {
1741+ m_notification_id = id;
1742+ }
1743+
1744+ static void on_notification_signal_static(GDBusConnection* /*connection*/,
1745+ const gchar* /*sender_name*/,
1746+ const gchar* object_path,
1747+ const gchar* interface_name,
1748+ const gchar* signal_name,
1749+ GVariant* parameters,
1750+ gpointer gself)
1751+ {
1752+ g_return_if_fail(!g_strcmp0(object_path, DBusNames::Notify::PATH));
1753+ g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Notify::INTERFACE));
1754+
1755+ auto self = static_cast<Impl*>(gself);
1756+
1757+ if (!g_strcmp0(signal_name, DBusNames::Notify::ActionInvoked::NAME))
1758+ {
1759+ uint32_t id {};
1760+ const char* action_name {};
1761+ g_variant_get(parameters, "(u&s)", &id, &action_name);
1762+ if (id == self->m_notification_id)
1763+ self->on_action_invoked(action_name);
1764+ }
1765+ else if (!g_strcmp0(signal_name, DBusNames::Notify::NotificationClosed::NAME))
1766+ {
1767+ uint32_t id {};
1768+ uint32_t close_reason {};
1769+ g_variant_get(parameters, "(uu)", &id, &close_reason);
1770+ if (id == self->m_notification_id)
1771+ self->on_notification_closed(close_reason);
1772+ }
1773+ }
1774+
1775+ void on_action_invoked(const char* action_name)
1776+ {
1777+ const auto response = !g_strcmp0(action_name, ACTION_ALLOW)
1778+ ? AdbdClient::PKResponse::ALLOW
1779+ : AdbdClient::PKResponse::DENY;
1780+
1781+ // FIXME: the current default is to cover the most common use case.
1782+ // We need to get the notification ui's checkbox working ASAP so
1783+ // that the user can provide this flag
1784+ const bool remember_this_choice = response == AdbdClient::PKResponse::ALLOW;
1785+
1786+ m_on_user_response(response, remember_this_choice);
1787+ }
1788+
1789+ void on_notification_closed(uint32_t close_reason)
1790+ {
1791+ if (close_reason == DBusNames::Notify::NotificationClosed::Reason::EXPIRED)
1792+ m_on_user_response(AdbdClient::PKResponse::DENY, false);
1793+
1794+ m_notification_id = 0;
1795+ }
1796+
1797+ static constexpr char const * ACTION_ALLOW {"allow"};
1798+ static constexpr char const * ACTION_DENY {"deny"};
1799+
1800+ const std::string m_fingerprint;
1801+ core::Signal<AdbdClient::PKResponse,bool> m_on_user_response;
1802+ GCancellable* m_cancellable {};
1803+ GDBusConnection* m_bus {};
1804+ uint32_t m_notification_id {};
1805+ unsigned int m_subscription_id {};
1806+};
1807+
1808+/***
1809+****
1810+***/
1811+
1812+UsbSnap::UsbSnap(const std::string& public_key):
1813+ impl{new Impl{public_key}}
1814+{
1815+}
1816+
1817+UsbSnap::~UsbSnap()
1818+{
1819+}
1820+
1821+core::Signal<AdbdClient::PKResponse,bool>&
1822+UsbSnap::on_user_response()
1823+{
1824+ return impl->on_user_response();
1825+}
1826+
1827
1828=== added file 'src/usb-snap.h'
1829--- src/usb-snap.h 1970-01-01 00:00:00 +0000
1830+++ src/usb-snap.h 2016-03-24 16:01:39 +0000
1831@@ -0,0 +1,42 @@
1832+/*
1833+ * Copyright 2016 Canonical Ltd.
1834+ *
1835+ * This program is free software: you can redistribute it and/or modify it
1836+ * under the terms of the GNU General Public License version 3, as published
1837+ * by the Free Software Foundation.
1838+ *
1839+ * This program is distributed in the hope that it will be useful, but
1840+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1841+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1842+ * PURPOSE. See the GNU General Public License for more details.
1843+ *
1844+ * You should have received a copy of the GNU General Public License along
1845+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1846+ *
1847+ * Authors:
1848+ * Charles Kerr <charles.kerr@canonical.com>
1849+ */
1850+
1851+#pragma once
1852+
1853+#include <src/adbd-client.h> // AdbdClient::PKResponse
1854+
1855+#include <core/signal.h>
1856+
1857+#include <memory>
1858+#include <string>
1859+
1860+/**
1861+ * A snap decision prompt for whether or not to allow an ADB connection
1862+ */
1863+class UsbSnap
1864+{
1865+public:
1866+ explicit UsbSnap(const std::string& public_key);
1867+ ~UsbSnap();
1868+ core::Signal<AdbdClient::PKResponse,bool>& on_user_response();
1869+
1870+protected:
1871+ class Impl;
1872+ std::unique_ptr<Impl> impl;
1873+};
1874
1875=== modified file 'tests/CMakeLists.txt'
1876--- tests/CMakeLists.txt 2014-08-21 03:35:16 +0000
1877+++ tests/CMakeLists.txt 2016-03-24 16:01:39 +0000
1878@@ -1,35 +1,35 @@
1879-include(FindGMock)
1880-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
1881-include_directories(${GMOCK_INCLUDE_DIRS})
1882-include_directories(${GTEST_INCLUDE_DIRS})
1883-
1884-# build libgtest
1885-#add_library (gtest STATIC
1886-# ${GTEST_SOURCE_DIR}/gtest-all.cc
1887-# ${GTEST_SOURCE_DIR}/gtest_main.cc)
1888-#set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR})
1889-#set_target_properties (gtest PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} -w)
1890-
1891-if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
1892- # turn off the warnings that break Google Test
1893- set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-global-constructors -Wno-weak-vtables -Wno-undef -Wno-c++98-compat-pedantic -Wno-missing-noreturn -Wno-used-but-marked-unused -Wno-padded -Wno-deprecated -Wno-sign-compare -Wno-shift-sign-overflow")
1894+set(CMAKE_AUTOMOC ON)
1895+find_package(GMock REQUIRED)
1896+find_package(Qt5Core REQUIRED)
1897+find_package(Qt5Test REQUIRED)
1898+find_package(Qt5DBus COMPONENTS Qt5DBusMacros REQUIRED)
1899+
1900+pkg_check_modules(TEST_DEPS
1901+ libqtdbustest-1 REQUIRED
1902+ libqtdbusmock-1 REQUIRED
1903+)
1904+
1905+include_directories(SYSTEM
1906+ ${DBUSTEST_INCLUDE_DIRS}
1907+ ${TEST_DEPS_INCLUDE_DIRS}
1908+ ${GTEST_INCLUDE_DIRS}
1909+ ${GMOCK_INCLUDE_DIRS}
1910+)
1911+
1912+list(APPEND CTEST_ENVIRONMENT
1913+ G_MESSAGES_DEBUG=all
1914+ G_DBUS_DEBUG=call,signal,return,message
1915+)
1916+
1917+# turn off the warnings that break Google Test
1918+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
1919+ list(APPEND CXX_WARNING_ARGS -Wno-global-constructors -Wno-weak-vtables)
1920 endif()
1921
1922-SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}")
1923-
1924-# look for headers in our src dir, and also in the directories where we autogenerate files...
1925-include_directories (${CMAKE_SOURCE_DIR}/src)
1926-include_directories (${CMAKE_CURRENT_BINARY_DIR})
1927-include_directories (${DBUSTEST_INCLUDE_DIRS})
1928-
1929-function(add_test_by_name name)
1930- set (TEST_NAME ${name})
1931- add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
1932- add_test (${TEST_NAME} ${TEST_NAME})
1933- add_dependencies (${TEST_NAME} libindicatordisplayservice)
1934- target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
1935-endfunction()
1936-add_test_by_name(test-rotation-lock)
1937-
1938-add_test (cppcheck cppcheck --enable=all -q --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests)
1939-
1940+add_compile_options(${CXX_WARNING_ARGS})
1941+
1942+add_test(cppcheck cppcheck --enable=all -USCHEMA_DIR --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests)
1943+
1944+add_subdirectory(integration)
1945+add_subdirectory(unit)
1946+add_subdirectory(utils)
1947
1948=== added directory 'tests/integration'
1949=== added file 'tests/integration/CMakeLists.txt'
1950--- tests/integration/CMakeLists.txt 1970-01-01 00:00:00 +0000
1951+++ tests/integration/CMakeLists.txt 2016-03-24 16:01:39 +0000
1952@@ -0,0 +1,24 @@
1953+set(SERVICE_LINK_LIBRARIES
1954+ ${SERVICE_LIB}
1955+ ${SERVICE_DEPS_LIBRARIES}
1956+)
1957+set(QT_LINK_LIBRARIES
1958+ test-utils
1959+ Qt5::Core
1960+ Qt5::Test
1961+ Qt5::DBus
1962+)
1963+set(TEST_LINK_LIBRARIES
1964+ ${TEST_DEPS_LIBRARIES}
1965+ ${GTEST_LIBRARIES}
1966+ ${GMOCK_LIBRARIES}
1967+)
1968+
1969+function(add_qt_test_by_name name)
1970+ set(TEST_NAME ${name})
1971+ add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
1972+ add_test(${TEST_NAME} ${TEST_NAME})
1973+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT})
1974+ target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES})
1975+endfunction()
1976+add_qt_test_by_name(usb-manager-test)
1977
1978=== added file 'tests/integration/usb-manager-test.cpp'
1979--- tests/integration/usb-manager-test.cpp 1970-01-01 00:00:00 +0000
1980+++ tests/integration/usb-manager-test.cpp 2016-03-24 16:01:39 +0000
1981@@ -0,0 +1,226 @@
1982+/*
1983+ * Copyright 2016 Canonical Ltd.
1984+ *
1985+ * This program is free software: you can redistribute it and/or modify it
1986+ * under the terms of the GNU General Public License version 3, as published
1987+ * by the Free Software Foundation.
1988+ *
1989+ * This program is distributed in the hope that it will be useful, but
1990+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1991+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1992+ * PURPOSE. See the GNU General Public License for more details.
1993+ *
1994+ * You should have received a copy of the GNU General Public License along
1995+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1996+ *
1997+ * Authors:
1998+ * Charles Kerr <charles.kerr@canonical.com>
1999+ */
2000+
2001+#include <tests/utils/adbd-server.h>
2002+#include <tests/utils/qt-fixture.h>
2003+#include <tests/utils/mock-greeter.h>
2004+#include <tests/utils/mock-usb-monitor.h>
2005+
2006+#include <src/dbus-names.h>
2007+#include <src/usb-manager.h>
2008+
2009+#include <libqtdbustest/DBusTestRunner.h>
2010+#include <libqtdbustest/QProcessDBusService.h>
2011+#include <libqtdbusmock/DBusMock.h>
2012+
2013+#include <fstream>
2014+#include <sstream>
2015+#include <vector>
2016+
2017+/***
2018+****
2019+***/
2020+
2021+class UsbManagerFixture: public QtFixture
2022+{
2023+ using super = QtFixture;
2024+
2025+public:
2026+
2027+ UsbManagerFixture():
2028+ dbusMock{dbusTestRunner}
2029+ {
2030+ dbusTestRunner.startServices();
2031+ }
2032+
2033+ ~UsbManagerFixture() =default;
2034+
2035+protected:
2036+
2037+ static void file_deleter (std::string* s)
2038+ {
2039+ fprintf(stderr, "remove \"%s\"\n", s->c_str());
2040+ g_remove(s->c_str());
2041+ delete s;
2042+ }
2043+
2044+ void SetUp() override
2045+ {
2046+ super::SetUp();
2047+
2048+ m_usb_monitor.reset(new MockUsbMonitor{});
2049+ m_greeter.reset(new MockGreeter{});
2050+
2051+ char tmpl[] = {"usb-manager-test-XXXXXX"};
2052+ m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter);
2053+ g_message("using tmpdir '%s'", m_tmpdir->c_str());
2054+
2055+ dbusMock.registerNotificationDaemon();
2056+ dbusTestRunner.startServices();
2057+ }
2058+
2059+ OrgFreedesktopDBusMockInterface& notificationsMockInterface()
2060+ {
2061+ return dbusMock.mockInterface(DBusNames::Notify::NAME,
2062+ DBusNames::Notify::PATH,
2063+ DBusNames::Notify::INTERFACE,
2064+ QDBusConnection::SessionBus);
2065+ }
2066+
2067+ QtDBusTest::DBusTestRunner dbusTestRunner;
2068+ QtDBusMock::DBusMock dbusMock;
2069+ std::shared_ptr<std::string> m_tmpdir;
2070+ std::shared_ptr<MockUsbMonitor> m_usb_monitor;
2071+ std::shared_ptr<MockGreeter> m_greeter;
2072+};
2073+
2074+TEST_F(UsbManagerFixture, Allow)
2075+{
2076+ const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter};
2077+ const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter};
2078+
2079+ // add a signal spy to listen to the notification daemon
2080+ QSignalSpy notificationsSpy(
2081+ &notificationsMockInterface(),
2082+ SIGNAL(MethodCalled(const QString &, const QVariantList &))
2083+ );
2084+
2085+ // start a mock AdbdServer ready to submit a request
2086+ const std::string public_key {"qAAAALUHllFjEZjl5jbS9ivjpQpaTNpibl28Re71D/S8sV3usNJTkbpvZYoVPfxtmHSNdCgLkWN6qcDZsHZqE/4myzmx/8Y/RqBy1oirudugi3YUUcJh7aWkY8lKQe9shCLTcrT7cFLZIJIidTvfmWTm0UcU+xmdPALze11I3lGo1Ty5KpCe9oP+qYM8suHbxhm78LKLlo0QJ2QqM8T5isr1pvoPHDgRb+mSESElG+xDIfPWA2BTu77/xk4EnXmOYfcuCr5akF3N4fRo/ACnYgXWDZFX2XdklBXyDj78lVlinF37xdMk7BMQh166X7UNkpH1uG2y5F6lUzyLg8SsFtRnJkw7eVe/gnJj3feQaFQbF5oVDhWhLMtWLtejhX6umvroVBVA4rynG4xEgs00K4u4ly8DUIIJYDO22Ml4myFR5CUm3lOlyitNdzYGh0utLXPq9oc8EbMVxM3i+O7PRxQw5Ul04X6K8GLiGUDV98DB+xYUqfEveq1BRnXi/ZrdPDhQ8Lfkg5xnLccPTFamAqutPtZXV6s7dXJInBTZf0NtBaWL0RdR2cOJBrpeBYkrc9yIyeqFLFdxr66rjaehjaa4pS4S+CD6PkGiIpPWSQtwNC4RlT10qTQ0/K9lRux2p0D8Z8ubUTFuh4kBScGUkN1OV3Z+7d7B+ghmBtZrrgleXsbehjRuKgEAAQA= foo@bar"};
2087+ const std::string fingerprint {"12:23:5f:2d:8c:40:ae:1d:05:7b:ae:bd:88:8a:f0:80"};
2088+ auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key});
2089+
2090+ // set up a UsbManager to process the request
2091+ auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter);
2092+
2093+ // wait for the notification to show up, confirm it looks right
2094+ wait_for_signals(notificationsSpy, 1);
2095+ {
2096+ QVariantList const& call(notificationsSpy.at(0));
2097+ EXPECT_EQ("Notify", call.at(0));
2098+
2099+ QVariantList const& args(call.at(1).toList());
2100+ ASSERT_EQ(8, args.size());
2101+ EXPECT_EQ("", args.at(0)); // app name
2102+ EXPECT_EQ(0, args.at(1)); // replaces-id
2103+ EXPECT_EQ("computer-symbolic", args.at(2)); // icon name
2104+ EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary
2105+ EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + QString::fromUtf8(fingerprint.c_str()), args.at(4)); // body
2106+ EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions
2107+ EXPECT_EQ(-1, args.at(7));
2108+
2109+ QVariantMap hints;
2110+ ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
2111+ ASSERT_EQ(3, hints.size());
2112+ ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint"));
2113+ ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
2114+ ASSERT_TRUE(hints.contains("x-canonical-snap-decisions"));
2115+ }
2116+ notificationsSpy.clear();
2117+
2118+ // click on allow in the notification
2119+ notificationsMockInterface().EmitSignal(
2120+ DBusNames::Notify::INTERFACE,
2121+ DBusNames::Notify::ActionInvoked::NAME,
2122+ "us",
2123+ QVariantList() << uint32_t(1) << "allow"
2124+ );
2125+
2126+ // confirm that the AdbdServer got the right response
2127+ wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000);
2128+ ASSERT_EQ(1, adbd_server->m_responses.size());
2129+ EXPECT_EQ("OK", adbd_server->m_responses.front());
2130+
2131+ // confirm that the public_keys file got the public key appended to it
2132+ std::ifstream ifkeys {*public_keys_path};
2133+ std::vector<std::string> lines;
2134+ std::string line;
2135+ while(getline(ifkeys, line))
2136+ lines.emplace_back(std::move(line));
2137+ ASSERT_EQ(1, lines.size());
2138+ EXPECT_EQ(public_key, lines[0]);
2139+}
2140+
2141+TEST_F(UsbManagerFixture, USBDisconnectedDuringPrompt)
2142+{
2143+ const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter};
2144+ const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter};
2145+
2146+ // start a mock AdbdServer ready to submit a request
2147+ const std::string public_key {"public_key"};
2148+ auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key});
2149+
2150+ // set up a UsbManager to process the request
2151+ auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter);
2152+
2153+ for (int i=0; i<3; i++)
2154+ {
2155+ // add a signal spy to listen to the notification daemon
2156+ QSignalSpy notificationsSpy(
2157+ &notificationsMockInterface(),
2158+ SIGNAL(MethodCalled(const QString &, const QVariantList &))
2159+ );
2160+
2161+ // wait for a notification to show up
2162+ wait_for_signals(notificationsSpy, 1);
2163+ EXPECT_EQ("Notify", notificationsSpy.at(0).at(0));
2164+ notificationsSpy.clear();
2165+
2166+ // wait for UsbSnap to receive dbusmock's response to the Notify request.
2167+ // there's no event to key off of for this, so just wait for a moment
2168+ wait_msec();
2169+
2170+ // disconnect the USB before the user has a chance to allow/deny
2171+ m_usb_monitor->m_on_usb_disconnected("android0");
2172+
2173+ // confirm that we requested the notification to be pulled down
2174+ wait_for_signals(notificationsSpy, 1);
2175+ EXPECT_EQ("CloseNotification", notificationsSpy.at(0).at(0));
2176+ notificationsSpy.clear();
2177+ }
2178+}
2179+
2180+TEST_F(UsbManagerFixture, Greeter)
2181+{
2182+ const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter};
2183+ const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter};
2184+
2185+ // start a mock AdbdServer ready to submit a request
2186+ const std::string public_key {"public_key"};
2187+ auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key});
2188+
2189+ // set up a UsbManager to process the request
2190+ m_greeter->m_is_active.set(true);
2191+ auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter);
2192+
2193+ // add a signal spy to listen to the notification daemon
2194+ QSignalSpy notificationsSpy(
2195+ &notificationsMockInterface(),
2196+ SIGNAL(MethodCalled(const QString &, const QVariantList &))
2197+ );
2198+
2199+ // the greeter is active, so the notification should not appear
2200+ EXPECT_FALSE(notificationsSpy.wait(2000));
2201+
2202+ // disable the greeter, the notification should appear
2203+ m_greeter->m_is_active.set(false);
2204+ wait_for_signals(notificationsSpy, 1);
2205+ EXPECT_EQ("Notify", notificationsSpy.at(0).at(0));
2206+ notificationsSpy.clear();
2207+}
2208
2209=== added directory 'tests/unit'
2210=== added file 'tests/unit/CMakeLists.txt'
2211--- tests/unit/CMakeLists.txt 1970-01-01 00:00:00 +0000
2212+++ tests/unit/CMakeLists.txt 2016-03-24 16:01:39 +0000
2213@@ -0,0 +1,34 @@
2214+set(SERVICE_LINK_LIBRARIES
2215+ ${SERVICE_LIB}
2216+ ${SERVICE_DEPS_LIBRARIES}
2217+)
2218+set(QT_LINK_LIBRARIES
2219+ test-utils
2220+ Qt5::Core
2221+ Qt5::Test
2222+ Qt5::DBus
2223+)
2224+set(TEST_LINK_LIBRARIES
2225+ ${TEST_DEPS_LIBRARIES}
2226+ ${GTEST_LIBRARIES}
2227+ ${GMOCK_LIBRARIES}
2228+)
2229+
2230+function(add_test_by_name name)
2231+ set(TEST_NAME ${name})
2232+ add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
2233+ add_test(${TEST_NAME} ${TEST_NAME})
2234+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT})
2235+ target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES})
2236+endfunction()
2237+add_test_by_name(adbd-client-test)
2238+add_test_by_name(rotation-lock-test)
2239+
2240+function(add_qt_test_by_name name)
2241+ set(TEST_NAME ${name})
2242+ add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
2243+ add_test(${TEST_NAME} ${TEST_NAME})
2244+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT})
2245+ target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES})
2246+endfunction()
2247+add_qt_test_by_name(usb-snap-test)
2248
2249=== added file 'tests/unit/adbd-client-test.cpp'
2250--- tests/unit/adbd-client-test.cpp 1970-01-01 00:00:00 +0000
2251+++ tests/unit/adbd-client-test.cpp 2016-03-24 16:01:39 +0000
2252@@ -0,0 +1,95 @@
2253+/*
2254+ * Copyright 2016 Canonical Ltd.
2255+ *
2256+ * This program is free software: you can redistribute it and/or modify it
2257+ * under the terms of the GNU General Public License version 3, as published
2258+ * by the Free Software Foundation.
2259+ *
2260+ * This program is distributed in the hope that it will be useful, but
2261+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2262+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2263+ * PURPOSE. See the GNU General Public License for more details.
2264+ *
2265+ * You should have received a copy of the GNU General Public License along
2266+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2267+ *
2268+ * Authors:
2269+ * Charles Kerr <charles.kerr@canonical.com>
2270+ */
2271+
2272+#include <tests/utils/test-dbus-fixture.h>
2273+#include <tests/utils/adbd-server.h>
2274+
2275+#include <src/adbd-client.h>
2276+
2277+class AdbdClientFixture: public TestDBusFixture
2278+{
2279+private:
2280+ typedef TestDBusFixture super;
2281+
2282+protected:
2283+
2284+ static void file_deleter (std::string* s)
2285+ {
2286+ fprintf(stderr, "remove \"%s\"\n", s->c_str());
2287+ g_remove(s->c_str());
2288+ delete s;
2289+ }
2290+
2291+ std::shared_ptr<std::string> m_tmpdir;
2292+
2293+ void SetUp()
2294+ {
2295+ super::SetUp();
2296+
2297+ char tmpl[] = {"adb-client-test-XXXXXX"};
2298+ m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter);
2299+ g_message("using tmpdir '%s'", m_tmpdir->c_str());
2300+ }
2301+};
2302+
2303+
2304+TEST_F(AdbdClientFixture, SocketPlumbing)
2305+{
2306+ struct {
2307+ const std::string request;
2308+ const std::string expected_pk;
2309+ AdbdClient::PKResponse response;
2310+ const std::string expected_response;
2311+ } tests[] = {
2312+ { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::ALLOW, "OK" },
2313+ { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::DENY, "NO" },
2314+ { "PKFooBar", "FooBar", AdbdClient::PKResponse::ALLOW, "OK" },
2315+ { "PK", "", AdbdClient::PKResponse::DENY, "NO" }
2316+ };
2317+
2318+ const auto main_thread = g_thread_self();
2319+
2320+ const auto socket_path = *m_tmpdir + "/test-socket-plumbing";
2321+ g_message("socket_path is %s", socket_path.c_str());
2322+
2323+ for (const auto& test : tests)
2324+ {
2325+ // start an AdbdClient that listens for PKRequests
2326+ std::string pk;
2327+ auto adbd_client = std::make_shared<GAdbdClient>(socket_path);
2328+ adbd_client->on_pk_request().connect([&pk, main_thread, test](const AdbdClient::PKRequest& req){
2329+ EXPECT_EQ(main_thread, g_thread_self());
2330+ g_message("in on_pk_request with %s", req.public_key.c_str());
2331+ pk = req.public_key;
2332+ req.respond(test.response);
2333+ });
2334+
2335+ // start a mock AdbdServer with to fire test key requests and wait for a response
2336+ auto adbd_server = std::make_shared<GAdbdServer>(socket_path, std::vector<std::string>{test.request});
2337+ wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000);
2338+ EXPECT_EQ(test.expected_pk, pk);
2339+ ASSERT_EQ(1, adbd_server->m_responses.size());
2340+ EXPECT_EQ(test.expected_response, adbd_server->m_responses.front());
2341+
2342+ // cleanup
2343+ adbd_client.reset();
2344+ adbd_server.reset();
2345+ g_unlink(socket_path.c_str());
2346+ }
2347+}
2348
2349=== renamed file 'tests/test-rotation-lock.cpp' => 'tests/unit/rotation-lock-test.cpp'
2350--- tests/test-rotation-lock.cpp 2014-08-21 03:35:16 +0000
2351+++ tests/unit/rotation-lock-test.cpp 2016-03-24 16:01:39 +0000
2352@@ -17,14 +17,14 @@
2353 * Charles Kerr <charles.kerr@canonical.com>
2354 */
2355
2356-#include "gtestdbus-fixture.h"
2357+#include <tests/utils/test-dbus-fixture.h>
2358
2359 #include <src/rotation-lock.h>
2360
2361-class RotationLockFixture: public GTestDBusFixture
2362+class RotationLockFixture: public TestDBusFixture
2363 {
2364 private:
2365- typedef GTestDBusFixture super;
2366+ typedef TestDBusFixture super;
2367
2368 protected:
2369
2370
2371=== added file 'tests/unit/usb-snap-test.cpp'
2372--- tests/unit/usb-snap-test.cpp 1970-01-01 00:00:00 +0000
2373+++ tests/unit/usb-snap-test.cpp 2016-03-24 16:01:39 +0000
2374@@ -0,0 +1,143 @@
2375+/*
2376+ * Copyright 2016 Canonical Ltd.
2377+ *
2378+ * This program is free software: you can redistribute it and/or modify it
2379+ * under the terms of the GNU General Public License version 3, as published
2380+ * by the Free Software Foundation.
2381+ *
2382+ * This program is distributed in the hope that it will be useful, but
2383+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2384+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2385+ * PURPOSE. See the GNU General Public License for more details.
2386+ *
2387+ * You should have received a copy of the GNU General Public License along
2388+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2389+ *
2390+ * Authors:
2391+ * Charles Kerr <charles.kerr@canonical.com>
2392+ */
2393+
2394+#include <tests/utils/qt-fixture.h>
2395+
2396+#include <src/dbus-names.h>
2397+#include <src/usb-snap.h>
2398+
2399+#include <libqtdbustest/DBusTestRunner.h>
2400+#include <libqtdbustest/QProcessDBusService.h>
2401+#include <libqtdbusmock/DBusMock.h>
2402+
2403+class UsbSnapFixture: public QtFixture
2404+{
2405+ using super = QtFixture;
2406+
2407+public:
2408+
2409+ UsbSnapFixture():
2410+ dbusMock{dbusTestRunner}
2411+ {
2412+ dbusTestRunner.startServices();
2413+ }
2414+
2415+ ~UsbSnapFixture() =default;
2416+
2417+protected:
2418+
2419+ void SetUp() override
2420+ {
2421+ super::SetUp();
2422+
2423+ dbusMock.registerNotificationDaemon();
2424+ dbusTestRunner.startServices();
2425+ }
2426+
2427+ OrgFreedesktopDBusMockInterface& notificationsMockInterface()
2428+ {
2429+ return dbusMock.mockInterface(DBusNames::Notify::NAME,
2430+ DBusNames::Notify::PATH,
2431+ DBusNames::Notify::INTERFACE,
2432+ QDBusConnection::SessionBus);
2433+ }
2434+
2435+ QtDBusTest::DBusTestRunner dbusTestRunner;
2436+ QtDBusMock::DBusMock dbusMock;
2437+};
2438+
2439+TEST_F(UsbSnapFixture, TestRoundTrip)
2440+{
2441+ struct {
2442+ const char* fingerprint;
2443+ const char* action_to_invoke;
2444+ const AdbdClient::PKResponse expected_response;
2445+ } tests[] = {
2446+ { "Fingerprint", "allow", AdbdClient::PKResponse::ALLOW },
2447+ { "Fingerprint", "deny", AdbdClient::PKResponse::DENY }
2448+ };
2449+
2450+ uint32_t next_id = 1;
2451+ for(const auto& test : tests)
2452+ {
2453+ // Minor wart: we don't have a way of getting the fdo notification id
2454+ // from dbusmock so instead we copy its (simple) id generation here
2455+ const auto id = next_id++;
2456+
2457+ QSignalSpy notificationsSpy(
2458+ &notificationsMockInterface(),
2459+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
2460+
2461+ // start up a UsbSnap to ask about a fingerprint
2462+ auto snap = std::make_shared<UsbSnap>(test.fingerprint);
2463+ AdbdClient::PKResponse user_response {};
2464+ bool user_response_set = false;
2465+ snap->on_user_response().connect([&user_response,&user_response_set](AdbdClient::PKResponse response, bool /*remember*/){
2466+ user_response = response;
2467+ user_response_set = true;
2468+ });
2469+
2470+ // test that UsbSnap creates a fdo notification
2471+ wait_for_signals(notificationsSpy, 1);
2472+ {
2473+ QVariantList const& call(notificationsSpy.at(0));
2474+ EXPECT_EQ("Notify", call.at(0));
2475+
2476+ QVariantList const& args(call.at(1).toList());
2477+ ASSERT_EQ(8, args.size());
2478+ EXPECT_EQ("", args.at(0)); // app name
2479+ EXPECT_EQ(0, args.at(1)); // replaces-id
2480+ EXPECT_EQ("computer-symbolic", args.at(2)); // icon name
2481+ EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary
2482+ EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + test.fingerprint, args.at(4)); // body
2483+ EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions
2484+ EXPECT_EQ(-1, args.at(7));
2485+
2486+ QVariantMap hints;
2487+ ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
2488+ ASSERT_EQ(3, hints.size());
2489+ ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint"));
2490+ ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
2491+ ASSERT_TRUE(hints.contains("x-canonical-snap-decisions"));
2492+ }
2493+ notificationsSpy.clear();
2494+
2495+ // fake a user interaction with the fdo notification
2496+ notificationsMockInterface().EmitSignal(
2497+ DBusNames::Notify::INTERFACE,
2498+ DBusNames::Notify::ActionInvoked::NAME,
2499+ "us",
2500+ QVariantList() << id << test.action_to_invoke);
2501+
2502+ // test that UsbSnap emits on_user_response() as a result
2503+ wait_for([&user_response_set](){return user_response_set;});
2504+ EXPECT_TRUE(user_response_set);
2505+ ASSERT_EQ(test.expected_response, user_response);
2506+
2507+ // confirm that the snap dtor cleans up the notification
2508+ snap.reset();
2509+ wait_for_signals(notificationsSpy, 1);
2510+ {
2511+ QVariantList const& call(notificationsSpy.at(0));
2512+ EXPECT_EQ("CloseNotification", call.at(0));
2513+ QVariantList const& args(call.at(1).toList());
2514+ EXPECT_EQ(id, args.at(0));
2515+ }
2516+ }
2517+}
2518
2519=== added directory 'tests/utils'
2520=== added file 'tests/utils/CMakeLists.txt'
2521--- tests/utils/CMakeLists.txt 1970-01-01 00:00:00 +0000
2522+++ tests/utils/CMakeLists.txt 2016-03-24 16:01:39 +0000
2523@@ -0,0 +1,17 @@
2524+include_directories(
2525+ ${CMAKE_CURRENT_BINARY_DIR}
2526+ ${CMAKE_CURRENT_SOURCE_DIR}
2527+)
2528+
2529+add_library(
2530+ test-utils
2531+ STATIC
2532+ qmain.cpp
2533+)
2534+
2535+qt5_use_modules(
2536+ test-utils
2537+ Core
2538+ DBus
2539+)
2540+
2541
2542=== added file 'tests/utils/adbd-server.h'
2543--- tests/utils/adbd-server.h 1970-01-01 00:00:00 +0000
2544+++ tests/utils/adbd-server.h 2016-03-24 16:01:39 +0000
2545@@ -0,0 +1,150 @@
2546+/*
2547+ * Copyright 2016 Canonical Ltd.
2548+ *
2549+ * This program is free software: you can redistribute it and/or modify it
2550+ * under the terms of the GNU General Public License version 3, as published
2551+ * by the Free Software Foundation.
2552+ *
2553+ * This program is distributed in the hope that it will be useful, but
2554+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2555+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2556+ * PURPOSE. See the GNU General Public License for more details.
2557+ *
2558+ * You should have received a copy of the GNU General Public License along
2559+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2560+ *
2561+ * Authors:
2562+ * Charles Kerr <charles.kerr@canonical.com>
2563+ */
2564+
2565+#include <gio/gio.h>
2566+#include <gio/gunixsocketaddress.h>
2567+
2568+#include <string>
2569+#include <thread>
2570+#include <vector>
2571+
2572+
2573+/**
2574+ * A Mock ADBD server.
2575+ *
2576+ * Binds to a local domain socket, sends public key requests across it,
2577+ * and reads back the client's responses.
2578+ */
2579+class GAdbdServer
2580+{
2581+public:
2582+
2583+ GAdbdServer(const std::string& socket_path,
2584+ const std::vector<std::string>& requests):
2585+ m_requests{requests},
2586+ m_socket_path{socket_path},
2587+ m_cancellable{g_cancellable_new()},
2588+ m_worker_thread{&GAdbdServer::worker_func, this}
2589+ {
2590+ }
2591+
2592+ ~GAdbdServer()
2593+ {
2594+ // tell the worker thread to stop whatever it's doing and exit.
2595+ g_cancellable_cancel(m_cancellable);
2596+ m_worker_thread.join();
2597+ g_clear_object(&m_cancellable);
2598+ }
2599+
2600+ const std::vector<std::string> m_requests;
2601+ std::vector<std::string> m_responses;
2602+
2603+private:
2604+
2605+ void worker_func() // runs in worker thread
2606+ {
2607+ auto server_socket = create_server_socket(m_socket_path);
2608+ auto requests = m_requests;
2609+
2610+ GError* error {};
2611+ g_socket_listen (server_socket, &error);
2612+ g_assert_no_error (error);
2613+
2614+ while (!g_cancellable_is_cancelled(m_cancellable) && !requests.empty())
2615+ {
2616+ // wait for a client connection
2617+ g_message("GAdbdServer::Impl::worker_func() calling g_socket_accept()");
2618+ auto client_socket = g_socket_accept(server_socket, m_cancellable, &error);
2619+ if (error != nullptr) {
2620+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2621+ g_message("GAdbdServer: Error accepting socket connection: %s", error->message);
2622+ g_clear_error(&error);
2623+ break;
2624+ }
2625+
2626+ // pop the next request off the stack
2627+ auto request = requests.front();
2628+
2629+ // send the request
2630+ g_message("GAdbdServer::Impl::worker_func() sending req [%s]", request.c_str());
2631+ g_socket_send(client_socket,
2632+ request.c_str(),
2633+ request.size(),
2634+ m_cancellable,
2635+ &error);
2636+ if (error != nullptr) {
2637+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2638+ g_message("GAdbdServer: Error sending request: %s", error->message);
2639+ g_clear_error(&error);
2640+ g_clear_object(&client_socket);
2641+ break;
2642+ }
2643+
2644+ // read the response
2645+ g_message("GAdbdServer::Impl::worker_func() reading response");
2646+ char buf[4096];
2647+ const auto n_bytes = g_socket_receive(client_socket,
2648+ buf,
2649+ sizeof(buf),
2650+ m_cancellable,
2651+ &error);
2652+ if (error != nullptr) {
2653+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2654+ g_message("GAdbdServer: Error reading response: %s", error->message);
2655+ g_clear_error(&error);
2656+ g_clear_object(&client_socket);
2657+ continue;
2658+ }
2659+ const std::string response(buf, std::string::size_type(n_bytes));
2660+ g_message("server read %d bytes, got response: '%s'", int(n_bytes), response.c_str());
2661+ if (!response.empty()) {
2662+ m_responses.push_back(response);
2663+ requests.erase(requests.begin());
2664+ }
2665+
2666+ // cleanup
2667+ g_clear_object(&client_socket);
2668+ }
2669+
2670+ g_clear_object(&server_socket);
2671+ }
2672+
2673+ // bind to a local domain socket
2674+ static GSocket* create_server_socket(const std::string& socket_path)
2675+ {
2676+ GError* error {};
2677+ auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX,
2678+ G_SOCKET_TYPE_STREAM,
2679+ G_SOCKET_PROTOCOL_DEFAULT,
2680+ &error);
2681+ g_assert_no_error(error);
2682+ auto address = g_unix_socket_address_new (socket_path.c_str());
2683+ g_socket_bind (socket, address, false, &error);
2684+ g_assert_no_error (error);
2685+ g_clear_object (&address);
2686+
2687+ return socket;
2688+ }
2689+
2690+ const std::string m_socket_path;
2691+ GCancellable* m_cancellable = nullptr;
2692+ std::thread m_worker_thread;
2693+};
2694+
2695+
2696
2697=== added file 'tests/utils/dbus-types.h'
2698--- tests/utils/dbus-types.h 1970-01-01 00:00:00 +0000
2699+++ tests/utils/dbus-types.h 2016-03-24 16:01:39 +0000
2700@@ -0,0 +1,42 @@
2701+/*
2702+ * Copyright (C) 2013-2016 Canonical, Ltd.
2703+ *
2704+ * This program is free software: you can redistribute it and/or modify it
2705+ * under the terms of the GNU General Public License version 3, as published
2706+ * by the Free Software Foundation.
2707+ *
2708+ * This program is distributed in the hope that it will be useful, but
2709+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2710+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2711+ * PURPOSE. See the GNU General Public License for more details.
2712+ *
2713+ * You should have received a copy of the GNU General Public License along
2714+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2715+ *
2716+ * Author: Pete Woods <pete.woods@canonical.com>
2717+ */
2718+
2719+#pragma once
2720+
2721+#include <QDBusMetaType>
2722+#include <QtCore>
2723+#include <QString>
2724+#include <QVariantMap>
2725+
2726+typedef QMap<QString, QVariantMap> QVariantDictMap;
2727+Q_DECLARE_METATYPE(QVariantDictMap)
2728+
2729+typedef QMap<QString, QString> QStringMap;
2730+Q_DECLARE_METATYPE(QStringMap)
2731+
2732+namespace DBusTypes
2733+{
2734+ inline void registerMetaTypes()
2735+ {
2736+ qRegisterMetaType<QVariantDictMap>("QVariantDictMap");
2737+ qRegisterMetaType<QStringMap>("QStringMap");
2738+
2739+ qDBusRegisterMetaType<QVariantDictMap>();
2740+ qDBusRegisterMetaType<QStringMap>();
2741+ }
2742+}
2743
2744=== renamed file 'tests/glib-fixture.h' => 'tests/utils/glib-fixture.h'
2745--- tests/glib-fixture.h 2014-10-06 20:57:13 +0000
2746+++ tests/utils/glib-fixture.h 2016-03-24 16:01:39 +0000
2747@@ -1,5 +1,8 @@
2748 /*
2749- * Copyright 2014 Canonical Ltd.
2750+ * Copyright 2013 Canonical Ltd.
2751+ *
2752+ * Authors:
2753+ * Charles Kerr <charles.kerr@canonical.com>
2754 *
2755 * This program is free software: you can redistribute it and/or modify it
2756 * under the terms of the GNU General Public License version 3, as published
2757@@ -12,15 +15,13 @@
2758 *
2759 * You should have received a copy of the GNU General Public License along
2760 * with this program. If not, see <http://www.gnu.org/licenses/>.
2761- *
2762- * Authors:
2763- * Charles Kerr <charles.kerr@canonical.com>
2764 */
2765
2766-#ifndef INDICATOR_TESTS_GLIB_FIXTURE_H
2767-#define INDICATOR_TESTS_GLIB_FIXTURE_H
2768+#pragma once
2769
2770+#include <functional> // std::function
2771 #include <map>
2772+#include <memory> // std::shared_ptr
2773
2774 #include <glib.h>
2775 #include <glib/gstdio.h>
2776@@ -32,81 +33,50 @@
2777
2778 class GlibFixture : public ::testing::Test
2779 {
2780- private:
2781-
2782- GLogFunc realLogHandler;
2783-
2784- std::map<GLogLevelFlags,size_t> expected_log;
2785- std::map<GLogLevelFlags,std::vector<std::string>> log;
2786-
2787- void test_log_counts()
2788- {
2789- const GLogLevelFlags levels_to_test[] = { G_LOG_LEVEL_ERROR,
2790- G_LOG_LEVEL_CRITICAL,
2791- G_LOG_LEVEL_MESSAGE,
2792- G_LOG_LEVEL_WARNING };
2793-
2794- for(const auto& level : levels_to_test)
2795- {
2796- const auto& v = log[level];
2797- const auto n = v.size();
2798-
2799- EXPECT_EQ(expected_log[level], n);
2800-
2801- if (expected_log[level] != n)
2802- for (size_t i=0; i<n; ++i)
2803- g_print("%d %s\n", (n+1), v[i].c_str());
2804- }
2805-
2806- expected_log.clear();
2807- log.clear();
2808- }
2809-
2810- static void default_log_handler(const gchar * log_domain,
2811- GLogLevelFlags log_level,
2812- const gchar * message,
2813- gpointer self)
2814- {
2815- char* tmp = g_strdup_printf ("%s:%d \"%s\"", log_domain, (int)log_level, message);
2816- static_cast<GlibFixture*>(self)->log[log_level].push_back(tmp);
2817- g_free(tmp);
2818- }
2819+ public:
2820+
2821+ virtual ~GlibFixture() =default;
2822
2823 protected:
2824
2825- void increment_expected_errors(GLogLevelFlags level, size_t n=1)
2826- {
2827- expected_log[level] += n;
2828- }
2829-
2830- virtual void SetUp()
2831+ virtual void SetUp() override
2832 {
2833 setlocale(LC_ALL, "C.UTF-8");
2834
2835 loop = g_main_loop_new(nullptr, false);
2836
2837- g_log_set_default_handler(default_log_handler, this);
2838-
2839+#ifdef SCHEMA_DIR
2840+ // only use local, temporary settings
2841+ g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true));
2842 g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true));
2843+ g_debug("SCHEMA_DIR is %s", SCHEMA_DIR);
2844+#endif
2845+
2846+ // fail on unexpected messages from this domain
2847+ g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING);
2848
2849 g_unsetenv("DISPLAY");
2850+
2851 }
2852
2853- virtual void TearDown()
2854+ virtual void TearDown() override
2855 {
2856- test_log_counts();
2857-
2858- g_log_set_default_handler(realLogHandler, this);
2859+ g_test_assert_expected_messages ();
2860
2861 g_clear_pointer(&loop, g_main_loop_unref);
2862 }
2863
2864+ void expectLogMessage (const gchar *domain, GLogLevelFlags level, const gchar *pattern)
2865+ {
2866+ g_test_expect_message (domain, level, pattern);
2867+ }
2868+
2869 private:
2870
2871 static gboolean
2872 wait_for_signal__timeout(gpointer name)
2873 {
2874- g_error("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name);
2875+ g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast<const char*>(name));
2876 return G_SOURCE_REMOVE;
2877 }
2878
2879@@ -120,7 +90,7 @@
2880 protected:
2881
2882 /* convenience func to loop while waiting for a GObject's signal */
2883- void wait_for_signal(gpointer o, const gchar * signal, const guint timeout_seconds=5)
2884+ void wait_for_signal(gpointer o, const gchar * signal, guint timeout_seconds=5)
2885 {
2886 // wait for the signal or for timeout, whichever comes first
2887 const auto handler_id = g_signal_connect_swapped(o, signal,
2888@@ -142,11 +112,92 @@
2889 g_source_remove(id);
2890 }
2891
2892+ bool wait_for(std::function<bool()> test_function, guint timeout_msec=1000)
2893+ {
2894+ auto timer = std::shared_ptr<GTimer>(g_timer_new(), [](GTimer* t){g_timer_destroy(t);});
2895+ const auto timeout_sec = timeout_msec / 1000.0;
2896+ for (;;) {
2897+ if (test_function())
2898+ return true;
2899+ //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec);
2900+ if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec)
2901+ return false;
2902+ wait_msec();
2903+ }
2904+ }
2905+
2906+ bool wait_for_name_owned(GDBusConnection* connection,
2907+ const gchar* name,
2908+ guint timeout_msec=1000,
2909+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
2910+ {
2911+ struct Data {
2912+ GMainLoop* loop = nullptr;
2913+ bool owned = false;
2914+ };
2915+ Data data;
2916+
2917+ auto on_name_appeared = [](GDBusConnection* /*connection*/,
2918+ const gchar* /*name_*/,
2919+ const gchar* name_owner,
2920+ gpointer gdata)
2921+ {
2922+ if (name_owner == nullptr)
2923+ return;
2924+ auto tmp = static_cast<Data*>(gdata);
2925+ tmp->owned = true;
2926+ g_main_loop_quit(tmp->loop);
2927+ };
2928+
2929+ const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop);
2930+ data.loop = loop;
2931+ const auto watch_id = g_bus_watch_name_on_connection(connection,
2932+ name,
2933+ flags,
2934+ on_name_appeared,
2935+ nullptr, /* name_vanished */
2936+ &data,
2937+ nullptr); /* user_data_free_func */
2938+ g_main_loop_run(loop);
2939+
2940+ g_bus_unwatch_name(watch_id);
2941+ g_source_remove(timeout_id);
2942+
2943+ return data.owned;
2944+ }
2945+
2946+ void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection,
2947+ const gchar* name,
2948+ guint timeout_msec=1000,
2949+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
2950+ {
2951+ EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
2952+ }
2953+
2954+ void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection,
2955+ const gchar* name,
2956+ guint timeout_msec=1000,
2957+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
2958+ {
2959+ EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
2960+ }
2961+
2962+ void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection,
2963+ const gchar* name,
2964+ guint timeout_msec=1000,
2965+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
2966+ {
2967+ ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
2968+ }
2969+
2970+ void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection,
2971+ const gchar* name,
2972+ guint timeout_msec=1000,
2973+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
2974+ {
2975+ ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
2976+ }
2977+
2978 GMainLoop * loop;
2979-
2980- public:
2981-
2982- virtual ~GlibFixture() =default;
2983 };
2984
2985-#endif /* INDICATOR_TESTS_GLIB_FIXTURE_H */
2986
2987=== added file 'tests/utils/gtest-qt-print-helpers.h'
2988--- tests/utils/gtest-qt-print-helpers.h 1970-01-01 00:00:00 +0000
2989+++ tests/utils/gtest-qt-print-helpers.h 2016-03-24 16:01:39 +0000
2990@@ -0,0 +1,45 @@
2991+
2992+#pragma once
2993+
2994+#include <QDBusObjectPath>
2995+#include <QString>
2996+#include <QStringList>
2997+#include <QVariant>
2998+
2999+inline QString qVariantToString(const QVariant& variant) {
3000+ QString output;
3001+ QDebug dbg(&output);
3002+ dbg << variant;
3003+ return output;
3004+}
3005+
3006+inline void PrintTo(const QVariant& variant, std::ostream* os) {
3007+ QString output;
3008+ QDebug dbg(&output);
3009+ dbg << variant;
3010+
3011+ *os << "QVariant(" << output.toStdString() << ")";
3012+}
3013+
3014+inline void PrintTo(const QString& s, std::ostream* os) {
3015+ *os << "\"" << s.toStdString() << "\"";
3016+}
3017+
3018+inline void PrintTo(const QStringList& list, std::ostream* os) {
3019+ QString output;
3020+ QDebug dbg(&output);
3021+ dbg << list;
3022+
3023+ *os << "QStringList(" << output.toStdString() << ")";
3024+}
3025+
3026+inline void PrintTo(const QList<QDBusObjectPath>& list, std::ostream* os) {
3027+ QString output;
3028+ for (const auto& path: list)
3029+ {
3030+ output.append("\"" + path.path() + "\",");
3031+ }
3032+
3033+ *os << "QList<QDBusObjectPath>(" << output.toStdString() << ")";
3034+}
3035+
3036
3037=== added file 'tests/utils/mock-greeter.h'
3038--- tests/utils/mock-greeter.h 1970-01-01 00:00:00 +0000
3039+++ tests/utils/mock-greeter.h 2016-03-24 16:01:39 +0000
3040@@ -0,0 +1,32 @@
3041+/*
3042+ * Copyright 2016 Canonical Ltd.
3043+ *
3044+ * This program is free software: you can redistribute it and/or modify it
3045+ * under the terms of the GNU General Public License version 3, as published
3046+ * by the Free Software Foundation.
3047+ *
3048+ * This program is distributed in the hope that it will be useful, but
3049+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3050+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3051+ * PURPOSE. See the GNU General Public License for more details.
3052+ *
3053+ * You should have received a copy of the GNU General Public License along
3054+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3055+ *
3056+ * Authors:
3057+ * Charles Kerr <charles.kerr@canonical.com>
3058+ */
3059+
3060+#pragma once
3061+
3062+#include <src/greeter.h>
3063+
3064+class MockGreeter: public Greeter
3065+{
3066+public:
3067+ MockGreeter() =default;
3068+ virtual ~MockGreeter() =default;
3069+ core::Property<bool>& is_active() override {return m_is_active;}
3070+ core::Property<bool> m_is_active {false};
3071+};
3072+
3073
3074=== added file 'tests/utils/mock-usb-monitor.h'
3075--- tests/utils/mock-usb-monitor.h 1970-01-01 00:00:00 +0000
3076+++ tests/utils/mock-usb-monitor.h 2016-03-24 16:01:39 +0000
3077@@ -0,0 +1,32 @@
3078+/*
3079+ * Copyright 2016 Canonical Ltd.
3080+ *
3081+ * This program is free software: you can redistribute it and/or modify it
3082+ * under the terms of the GNU General Public License version 3, as published
3083+ * by the Free Software Foundation.
3084+ *
3085+ * This program is distributed in the hope that it will be useful, but
3086+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3087+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3088+ * PURPOSE. See the GNU General Public License for more details.
3089+ *
3090+ * You should have received a copy of the GNU General Public License along
3091+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3092+ *
3093+ * Authors:
3094+ * Charles Kerr <charles.kerr@canonical.com>
3095+ */
3096+
3097+#pragma once
3098+
3099+#include <src/usb-monitor.h>
3100+
3101+class MockUsbMonitor: public UsbMonitor
3102+{
3103+public:
3104+ MockUsbMonitor() =default;
3105+ virtual ~MockUsbMonitor() =default;
3106+ core::Signal<const std::string&>& on_usb_disconnected() override {return m_on_usb_disconnected;}
3107+ core::Signal<const std::string&> m_on_usb_disconnected;
3108+};
3109+
3110
3111=== added file 'tests/utils/qmain.cpp'
3112--- tests/utils/qmain.cpp 1970-01-01 00:00:00 +0000
3113+++ tests/utils/qmain.cpp 2016-03-24 16:01:39 +0000
3114@@ -0,0 +1,60 @@
3115+/*
3116+ * Copyright © 2014 Canonical Ltd.
3117+ *
3118+ * This program is free software: you can redistribute it and/or modify it
3119+ * under the terms of the GNU Lesser General Public License version 3,
3120+ * as published by the Free Software Foundation.
3121+ *
3122+ * This program is distributed in the hope that it will be useful,
3123+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3124+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3125+ * GNU Lesser General Public License for more details.
3126+ *
3127+ * You should have received a copy of the GNU Lesser General Public License
3128+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3129+ *
3130+ * Authors:
3131+ * Pete Woods <pete.woods@canonical.com>
3132+ */
3133+
3134+//#include <config.h>
3135+
3136+#include <QCoreApplication>
3137+#include <QTimer>
3138+#include <gtest/gtest.h>
3139+
3140+#include <libqtdbusmock/DBusMock.h>
3141+
3142+using namespace QtDBusMock;
3143+
3144+class Runner: public QObject
3145+{
3146+ Q_OBJECT
3147+public Q_SLOTS:
3148+ void run()
3149+ {
3150+ QCoreApplication::exit(RUN_ALL_TESTS());
3151+ }
3152+};
3153+
3154+int main(int argc, char **argv)
3155+{
3156+ qputenv("LANG", "C.UTF-8");
3157+ unsetenv("LC_ALL");
3158+
3159+ // boilerplate i18n
3160+ setlocale(LC_ALL, "");
3161+ bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
3162+ textdomain(GETTEXT_PACKAGE);
3163+
3164+ QCoreApplication application(argc, argv);
3165+ DBusMock::registerMetaTypes();
3166+ ::testing::InitGoogleTest(&argc, argv);
3167+
3168+ Runner runner;
3169+ QTimer::singleShot(0, &runner, SLOT(run()));
3170+
3171+ return application.exec();
3172+}
3173+
3174+#include "qmain.moc"
3175
3176=== added file 'tests/utils/qt-fixture.h'
3177--- tests/utils/qt-fixture.h 1970-01-01 00:00:00 +0000
3178+++ tests/utils/qt-fixture.h 2016-03-24 16:01:39 +0000
3179@@ -0,0 +1,74 @@
3180+/*
3181+ * Copyright 2016 Canonical Ltd.
3182+ *
3183+ * This program is free software: you can redistribute it and/or modify it
3184+ * under the terms of the GNU General Public License version 3, as published
3185+ * by the Free Software Foundation.
3186+ *
3187+ * This program is distributed in the hope that it will be useful, but
3188+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3189+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3190+ * PURPOSE. See the GNU General Public License for more details.
3191+ *
3192+ * You should have received a copy of the GNU General Public License along
3193+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3194+ *
3195+ * Authors:
3196+ * Charles Kerr <charles.kerr@canonical.com>
3197+ */
3198+
3199+#pragma once
3200+
3201+#define QT_NO_KEYWORDS
3202+
3203+#include <tests/utils/dbus-types.h>
3204+#include <tests/utils/glib-fixture.h>
3205+#include <tests/utils/gtest-qt-print-helpers.h>
3206+
3207+#include <gtest/gtest.h>
3208+
3209+#include <QDBusArgument>
3210+#include <QVariant>
3211+#include <QSignalSpy>
3212+
3213+#define wait_for_signals(signalSpy,signalsExpected) \
3214+{ \
3215+ while (signalSpy.size() < signalsExpected) \
3216+ { \
3217+ ASSERT_TRUE(signalSpy.wait()); \
3218+ } \
3219+ \
3220+ ASSERT_EQ(signalsExpected, signalSpy.size()); \
3221+}
3222+
3223+class QtFixture: public GlibFixture
3224+{
3225+ using super = GlibFixture;
3226+
3227+public:
3228+
3229+ QtFixture()
3230+ {
3231+ DBusTypes::registerMetaTypes();
3232+ }
3233+
3234+ ~QtFixture() =default;
3235+
3236+protected:
3237+
3238+ bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map)
3239+ {
3240+ if (variant.canConvert<QDBusArgument>())
3241+ {
3242+ QDBusArgument value(variant.value<QDBusArgument>());
3243+ if (value.currentType() == QDBusArgument::MapType)
3244+ {
3245+ value >> map;
3246+ return true;
3247+ }
3248+ }
3249+
3250+ return false;
3251+ }
3252+};
3253+
3254
3255=== renamed file 'tests/gtestdbus-fixture.h' => 'tests/utils/test-dbus-fixture.h'
3256--- tests/gtestdbus-fixture.h 2014-10-06 19:54:15 +0000
3257+++ tests/utils/test-dbus-fixture.h 2016-03-24 16:01:39 +0000
3258@@ -17,8 +17,7 @@
3259 * Charles Kerr <charles.kerr@canonical.com>
3260 */
3261
3262-#ifndef INDICATOR_TESTS_GTESTDBUS_FIXTURE_H
3263-#define INDICATOR_TESTS_GTESTDBUS_FIXTURE_H
3264+#pragma once
3265
3266 #include "glib-fixture.h"
3267
3268@@ -26,14 +25,14 @@
3269 ****
3270 ***/
3271
3272-class GTestDBusFixture: public GlibFixture
3273+class TestDBusFixture: public GlibFixture
3274 {
3275 public:
3276
3277- GTestDBusFixture() =default;
3278- virtual ~GTestDBusFixture() =default;
3279+ TestDBusFixture() =default;
3280+ virtual ~TestDBusFixture() =default;
3281
3282- explicit GTestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {}
3283+ explicit TestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {}
3284
3285 private:
3286
3287@@ -42,10 +41,10 @@
3288 static void
3289 on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself)
3290 {
3291- auto self = static_cast<GTestDBusFixture*>(gself);
3292+ auto self = static_cast<TestDBusFixture*>(gself);
3293
3294 GError * err = 0;
3295- self->bus = g_bus_get_finish (res, &err);
3296+ self->system_bus = g_bus_get_finish (res, &err);
3297 g_assert_no_error (err);
3298
3299 g_main_loop_quit (self->loop);
3300@@ -54,10 +53,10 @@
3301 static void
3302 on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself)
3303 {
3304- auto self = static_cast<GTestDBusFixture*>(gself);
3305+ auto self = static_cast<TestDBusFixture*>(gself);
3306
3307 GError * err = 0;
3308- g_dbus_connection_close_finish (self->bus, res, &err);
3309+ g_dbus_connection_close_finish (self->system_bus, res, &err);
3310 g_assert_no_error (err);
3311
3312 g_main_loop_quit (self->loop);
3313@@ -66,10 +65,10 @@
3314 protected:
3315
3316 GTestDBus * test_dbus = nullptr;
3317- GDBusConnection * bus = nullptr;
3318+ GDBusConnection * system_bus = nullptr;
3319 const std::vector<std::string> service_dirs;
3320
3321- virtual void SetUp ()
3322+ virtual void SetUp() override
3323 {
3324 super::SetUp ();
3325
3326@@ -88,14 +87,14 @@
3327 g_main_loop_run (loop);
3328 }
3329
3330- virtual void TearDown ()
3331+ virtual void TearDown() override
3332 {
3333 wait_msec();
3334
3335 // close the system bus
3336- g_dbus_connection_close(bus, nullptr, on_bus_closed, this);
3337+ g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this);
3338 g_main_loop_run(loop);
3339- g_clear_object(&bus);
3340+ g_clear_object(&system_bus);
3341
3342 // tear down the test dbus
3343 g_test_dbus_down(test_dbus);
3344@@ -105,4 +104,3 @@
3345 }
3346 };
3347
3348-#endif /* INDICATOR_TESTS_GTESTDBUS_FIXTURE_H */

Subscribers

People subscribed via source and target branches