blob: 5559f9dd8e7ad166cfe340620b8be0df8001e1ea [file] [log] [blame]
Matt Mowere04eee72016-12-31 00:38:57 -06001/*
2 Copyright 2017 TeamWin
3 This file is part of TWRP/TeamWin Recovery Project.
4
5 TWRP is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 TWRP is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with TWRP. If not, see <http://www.gnu.org/licenses/>.
17*/
18
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010019#include <stdarg.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <fcntl.h>
24#include <sys/types.h>
25#include <time.h>
26#include <unistd.h>
27#include <stdlib.h>
28
29#include <string>
Aleksa Saraib25a1832015-12-31 17:36:00 +010030#include <sstream>
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010031
32extern "C" {
33#include "../twcommon.h"
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010034}
Ethan Yonkerfbb43532015-12-28 21:54:50 +010035#include "../minuitwrp/minui.h"
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -050036#include "../twrp-functions.hpp"
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010037#include "rapidxml.hpp"
38#include "objects.hpp"
39
40GUIPatternPassword::GUIPatternPassword(xml_node<>* node)
41 : GUIObject(node)
42{
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010043 xml_node<>* child;
44
Aleksa Saraib25a1832015-12-31 17:36:00 +010045 // 3x3 is the default.
46 mGridSize = 3;
47 mDots = new Dot[mGridSize * mGridSize];
48 mConnectedDots = new int[mGridSize * mGridSize];
49
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010050 ResetActiveDots();
51 mTrackingTouch = false;
52 mNeedRender = true;
53
54 ConvertStrToColor("blue", &mDotColor);
55 ConvertStrToColor("white", &mActiveDotColor);
56 ConvertStrToColor("blue", &mLineColor);
57
58 mDotImage = mActiveDotImage = NULL;
59 mDotCircle = mActiveDotCircle = NULL;
60 mDotRadius = 50;
61 mLineWidth = 35;
62
63 mAction = NULL;
64 mUpdate = 0;
65
66 if (!node)
67 return;
68
69 LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &mPlacement);
70
71 mAction = new GUIAction(node);
72
73 child = FindNode(node, "dot");
Matt Mowera8a89d12016-12-30 18:10:37 -060074 if (child)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010075 {
76 mDotColor = LoadAttrColor(child, "color", mDotColor);
77 mActiveDotColor = LoadAttrColor(child, "activecolor", mActiveDotColor);
78 mDotRadius = LoadAttrIntScaleX(child, "radius", mDotRadius);
79
80 mDotImage = LoadAttrImage(child, "image");
81 mActiveDotImage = LoadAttrImage(child, "activeimage");
82 }
83
84 child = FindNode(node, "line");
Matt Mowera8a89d12016-12-30 18:10:37 -060085 if (child)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010086 {
87 mLineColor = LoadAttrColor(child, "color", mLineColor);
88 mLineWidth = LoadAttrIntScaleX(child, "width", mLineWidth);
89 }
90
91 child = FindNode(node, "data");
Matt Mowera8a89d12016-12-30 18:10:37 -060092 if (child)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010093 mPassVar = LoadAttrString(child, "name", "");
94
Aleksa Saraib25a1832015-12-31 17:36:00 +010095 child = FindNode(node, "size");
Matt Mowera8a89d12016-12-30 18:10:37 -060096 if (child) {
Aleksa Saraib25a1832015-12-31 17:36:00 +010097 mSizeVar = LoadAttrString(child, "name", "");
98
99 // Use the configured default, if set.
100 size_t size = LoadAttrInt(child, "default", mGridSize);
101 Resize(size);
102 }
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100103
Matt Mowera8a89d12016-12-30 18:10:37 -0600104 if (!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource())
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100105 {
106 mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha);
107 mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha);
108 }
Ethan Yonker58f21322018-08-24 11:17:36 -0500109 else if (mDotImage && mDotImage->GetResource())
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100110 mDotRadius = mDotImage->GetWidth()/2;
111
112 SetRenderPos(mRenderX, mRenderY, mRenderW, mRenderH);
113}
114
115GUIPatternPassword::~GUIPatternPassword()
116{
117 delete mDotImage;
118 delete mActiveDotImage;
119 delete mAction;
120
Aleksa Saraib25a1832015-12-31 17:36:00 +0100121 delete[] mDots;
122 delete[] mConnectedDots;
123
Matt Mowera8a89d12016-12-30 18:10:37 -0600124 if (mDotCircle)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100125 gr_free_surface(mDotCircle);
126
Matt Mowera8a89d12016-12-30 18:10:37 -0600127 if (mActiveDotCircle)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100128 gr_free_surface(mActiveDotCircle);
129}
130
131void GUIPatternPassword::ResetActiveDots()
132{
133 mConnectedDotsLen = 0;
134 mCurLineX = mCurLineY = -1;
Matt Mowera8a89d12016-12-30 18:10:37 -0600135 for (size_t i = 0; i < mGridSize * mGridSize; ++i)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100136 mDots[i].active = false;
137}
138
139int GUIPatternPassword::SetRenderPos(int x, int y, int w, int h)
140{
141 mRenderX = x;
142 mRenderY = y;
143
144 if (w || h)
145 {
146 mRenderW = w;
147 mRenderH = h;
148
149 mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
150 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
151 }
152
153 CalculateDotPositions();
154 return 0;
155}
156
157void GUIPatternPassword::CalculateDotPositions(void)
158{
Aleksa Saraib25a1832015-12-31 17:36:00 +0100159 const int num_gaps = mGridSize - 1;
160 const int step_x = (mRenderW - mDotRadius*2) / num_gaps;
161 const int step_y = (mRenderH - mDotRadius*2) / num_gaps;
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100162 int x = mRenderX;
163 int y = mRenderY;
164
Aleksa Saraib25a1832015-12-31 17:36:00 +0100165 /* Order is important for keyphrase generation:
166 *
167 * 0 1 2 3 ... n-1
168 * n n+1 n+2 n+3 ... 2n-1
169 * 2n 2n+1 2n+2 2n+3 ... 3n-1
170 * 3n 3n+1 3n+2 3n+3 ... 4n-1
171 * : : : :
172 * n*n-1
173 */
174
Matt Mowera8a89d12016-12-30 18:10:37 -0600175 for (size_t r = 0; r < mGridSize; ++r)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100176 {
Matt Mowera8a89d12016-12-30 18:10:37 -0600177 for (size_t c = 0; c < mGridSize; ++c)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100178 {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100179 mDots[mGridSize*r + c].x = x;
180 mDots[mGridSize*r + c].y = y;
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100181 x += step_x;
182 }
183 x = mRenderX;
184 y += step_y;
185 }
186}
187
188int GUIPatternPassword::Render(void)
189{
Matt Mowera8a89d12016-12-30 18:10:37 -0600190 if (!isConditionTrue())
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100191 return 0;
192
193 gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
Matt Mowera8a89d12016-12-30 18:10:37 -0600194 for (size_t i = 1; i < mConnectedDotsLen; ++i) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100195 const Dot& dp = mDots[mConnectedDots[i-1]];
196 const Dot& dc = mDots[mConnectedDots[i]];
197 gr_line(dp.x + mDotRadius, dp.y + mDotRadius, dc.x + mDotRadius, dc.y + mDotRadius, mLineWidth);
198 }
199
Matt Mowera8a89d12016-12-30 18:10:37 -0600200 if (mConnectedDotsLen > 0 && mTrackingTouch) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100201 const Dot& dc = mDots[mConnectedDots[mConnectedDotsLen-1]];
202 gr_line(dc.x + mDotRadius, dc.y + mDotRadius, mCurLineX, mCurLineY, mLineWidth);
203 }
204
Matt Mowera8a89d12016-12-30 18:10:37 -0600205 for (size_t i = 0; i < mGridSize * mGridSize; ++i) {
206 if (mDotCircle) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100207 gr_blit(mDotCircle, 0, 0, gr_get_width(mDotCircle), gr_get_height(mDotCircle), mDots[i].x, mDots[i].y);
Matt Mowera8a89d12016-12-30 18:10:37 -0600208 if (mDots[i].active) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100209 gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2);
210 }
211 } else {
Ethan Yonker58f21322018-08-24 11:17:36 -0500212 if (mDots[i].active && mActiveDotImage && mActiveDotImage->GetResource()) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100213 gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(),
214 mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2));
Ethan Yonker58f21322018-08-24 11:17:36 -0500215 } else if (mDotImage && mDotImage->GetResource()) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100216 gr_blit(mDotImage->GetResource(), 0, 0, mDotImage->GetWidth(), mDotImage->GetHeight(), mDots[i].x, mDots[i].y);
217 }
218 }
219 }
220 return 0;
221}
222
223int GUIPatternPassword::Update(void)
224{
Matt Mowera8a89d12016-12-30 18:10:37 -0600225 if (!isConditionTrue())
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100226 return 0;
227
228 int res = mNeedRender ? 2 : 1;
229 mNeedRender = false;
230 return res;
231}
232
Aleksa Saraib25a1832015-12-31 17:36:00 +0100233void GUIPatternPassword::Resize(size_t n) {
Matt Mowera8a89d12016-12-30 18:10:37 -0600234 if (mGridSize == n)
Aleksa Saraib25a1832015-12-31 17:36:00 +0100235 return;
236
237 delete[] mDots;
238 delete[] mConnectedDots;
239
240 mGridSize = n;
241 mDots = new Dot[n*n];
242 mConnectedDots = new int[n*n];
243
244 ResetActiveDots();
245 CalculateDotPositions();
246 mTrackingTouch = false;
247 mNeedRender = true;
248}
249
250static int pow(int x, int i)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100251{
nailyk61c7c5c2016-08-19 13:36:07 +0200252 int result = 1;
253 if (i<0)
254 return 0;
255 while(i-- > 0)
256 result *= x;
257 return result;
Aleksa Saraib25a1832015-12-31 17:36:00 +0100258}
259
260static bool IsInCircle(int x, int y, int ox, int oy, int r)
261{
262 return pow(x - ox, 2) + pow(y - oy, 2) <= pow(r, 2);
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100263}
264
265int GUIPatternPassword::InDot(int x, int y)
266{
Matt Mowera8a89d12016-12-30 18:10:37 -0600267 for (size_t i = 0; i < mGridSize * mGridSize; ++i) {
268 if (IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3))
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100269 return i;
270 }
271 return -1;
272}
273
274bool GUIPatternPassword::DotUsed(int dot_idx)
275{
Matt Mowera8a89d12016-12-30 18:10:37 -0600276 for (size_t i = 0; i < mConnectedDotsLen; ++i) {
277 if (mConnectedDots[i] == dot_idx)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100278 return true;
279 }
280 return false;
281}
282
283void GUIPatternPassword::ConnectDot(int dot_idx)
284{
Matt Mowera8a89d12016-12-30 18:10:37 -0600285 if (mConnectedDotsLen >= mGridSize * mGridSize)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100286 {
287 LOGERR("mConnectedDots in GUIPatternPassword has overflown!\n");
288 return;
289 }
290
291 mConnectedDots[mConnectedDotsLen++] = dot_idx;
292 mDots[dot_idx].active = true;
293}
294
Aleksa Saraib25a1832015-12-31 17:36:00 +0100295void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100296{
Matt Mowera8a89d12016-12-30 18:10:37 -0600297 if (mConnectedDotsLen == 0)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100298 return;
299
Aleksa Saraib25a1832015-12-31 17:36:00 +0100300 const int prev_dot_idx = mConnectedDots[mConnectedDotsLen-1];
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100301
Aleksa Saraib25a1832015-12-31 17:36:00 +0100302 int px = prev_dot_idx % mGridSize;
303 int py = prev_dot_idx / mGridSize;
304
305 int nx = next_dot_idx % mGridSize;
306 int ny = next_dot_idx / mGridSize;
307
308 /*
309 * We connect all dots that are in a straight line between the previous dot
310 * and the next one. This is simple for 3x3, but is more complicated for
311 * larger grids.
312 *
313 * Weirdly, Android doesn't do the logical thing when it comes to connecting
314 * dots between two points. Rather than simply adding all points that lie
315 * on the line between the start and end points, it instead only connects
316 * dots that are adjacent in only three directions -- horizontal, vertical
317 * and diagonal (45°).
318 *
319 * So we can just iterate over the correct axes, taking care to ensure that
320 * the order in which the intermediate points are added to the pattern is
321 * correct.
322 */
323
324 int x = px;
325 int y = py;
326
327 int Dx = (nx > px) ? 1 : -1;
328 int Dy = (ny > py) ? 1 : -1;
329
330 // Vertical lines.
Matt Mowera8a89d12016-12-30 18:10:37 -0600331 if (px == nx)
Aleksa Saraib25a1832015-12-31 17:36:00 +0100332 Dx = 0;
333
334 // Horizontal lines.
Matt Mowera8a89d12016-12-30 18:10:37 -0600335 else if (py == ny)
Aleksa Saraib25a1832015-12-31 17:36:00 +0100336 Dy = 0;
337
338 // Diagonal lines (|∆x| = |∆y|).
Matt Mowera8a89d12016-12-30 18:10:37 -0600339 else if (abs(px - nx) == abs(py - ny))
Aleksa Saraib25a1832015-12-31 17:36:00 +0100340 ;
341
342 // No valid intermediate dots.
343 else
Vojtech Boceke8c39272015-03-15 15:25:37 +0100344 return;
Vojtech Boceke8c39272015-03-15 15:25:37 +0100345
Aleksa Saraib25a1832015-12-31 17:36:00 +0100346 // Iterate along axis, adding dots in the correct order.
Matt Mowera8a89d12016-12-30 18:10:37 -0600347 while ((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100348 x += Dx;
349 y += Dy;
350
351 int idx = mGridSize * y + x;
Matt Mowera8a89d12016-12-30 18:10:37 -0600352 if (!DotUsed(idx))
Aleksa Saraib25a1832015-12-31 17:36:00 +0100353 ConnectDot(idx);
354 }
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100355}
356
357int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y)
358{
Matt Mowera8a89d12016-12-30 18:10:37 -0600359 if (!isConditionTrue())
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100360 return -1;
361
362 switch (state)
363 {
364 case TOUCH_START:
365 {
366 const int dot_idx = InDot(x, y);
Matt Mowera8a89d12016-12-30 18:10:37 -0600367 if (dot_idx == -1)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100368 break;
369
370 mTrackingTouch = true;
371 ResetActiveDots();
372 ConnectDot(dot_idx);
373 DataManager::Vibrate("tw_button_vibrate");
374 mCurLineX = x;
375 mCurLineY = y;
376 mNeedRender = true;
377 break;
378 }
379 case TOUCH_DRAG:
380 {
Matt Mowera8a89d12016-12-30 18:10:37 -0600381 if (!mTrackingTouch)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100382 break;
383
384 const int dot_idx = InDot(x, y);
Matt Mowera8a89d12016-12-30 18:10:37 -0600385 if (dot_idx != -1 && !DotUsed(dot_idx))
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100386 {
387 ConnectIntermediateDots(dot_idx);
388 ConnectDot(dot_idx);
389 DataManager::Vibrate("tw_button_vibrate");
390 }
391
392 mCurLineX = x;
393 mCurLineY = y;
394 mNeedRender = true;
395 break;
396 }
397 case TOUCH_RELEASE:
398 {
Matt Mowera8a89d12016-12-30 18:10:37 -0600399 if (!mTrackingTouch)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100400 break;
401
402 mNeedRender = true;
403 mTrackingTouch = false;
404 PatternDrawn();
405 ResetActiveDots();
406 break;
407 }
408 default:
409 break;
410 }
411 return 0;
412}
413
Aleksa Saraib25a1832015-12-31 17:36:00 +0100414int GUIPatternPassword::NotifyVarChange(const std::string& varName, const std::string& value)
415{
Matt Mowera8a89d12016-12-30 18:10:37 -0600416 if (!isConditionTrue())
Aleksa Saraib25a1832015-12-31 17:36:00 +0100417 return 0;
418
Matt Mowera8a89d12016-12-30 18:10:37 -0600419 if (varName == mSizeVar) {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100420 Resize(atoi(value.c_str()));
421 mUpdate = true;
422 }
423 return 0;
424}
425
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -0500426static unsigned int getSDKVersion(void) {
427 unsigned int sdkver = 23;
428 string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
429 if (!sdkverstr.empty()) {
430 sdkver = (unsigned int)strtoull(sdkverstr.c_str(), NULL, 10);
431 sdkver = (sdkver != 0) ? sdkver : 23;
432 }
433 LOGINFO("sdk version is %u\n", sdkver);
434 return sdkver;
435}
436
Aleksa Saraib25a1832015-12-31 17:36:00 +0100437std::string GUIPatternPassword::GeneratePassphrase()
438{
439 char pattern[mConnectedDotsLen];
Matt Mowera8a89d12016-12-30 18:10:37 -0600440 for (size_t i = 0; i < mConnectedDotsLen; i++) {
441 pattern[i] = (char) mConnectedDots[i];
Aleksa Saraib25a1832015-12-31 17:36:00 +0100442 }
443
444 std::stringstream pass;
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -0500445 char buffer[3] = {0};
Aleksa Saraib25a1832015-12-31 17:36:00 +0100446
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -0500447 if ((mGridSize == 3) || (getSDKVersion() >= 23)) {
448 // Marshmallow uses a consistent method
449 for (size_t i = 0; i < mConnectedDotsLen; i++) {
450 buffer[0] = (pattern[i] & 0xff) + '1';
451 pass << std::string(buffer);
452 }
453 } else {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100454 /*
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -0500455 * Okay, rant time for pre-Marshmallow ROMs.
Aleksa Saraib25a1832015-12-31 17:36:00 +0100456 * It turns out that Android and CyanogenMod have *two* separate methods
457 * for generating passphrases from patterns. This is a legacy issue, as
458 * Android only supports 3x3 grids, and so we need to support both.
459 * Luckily, CyanogenMod is in the same boat as us and needs to support
460 * Android's 3x3 encryption style.
461 *
462 * In order to generate a 3x3 passphrase, add 1 to each dot index
463 * and concatenate the string representation of the integers. No
464 * padding should be added.
465 *
466 * For *all* other NxN passphrases (until a 16x16 grid comes along),
467 * they are generated by taking "%.2x" for each dot index and
468 * concatenating the results (without adding 1).
469 */
Sultan Qasim Khan7a48a662016-02-12 19:17:38 -0500470 for (size_t i = 0; i < mConnectedDotsLen; i++) {
471 snprintf(buffer, 3, "%.2x", pattern[i] & 0xff);
Aleksa Saraib25a1832015-12-31 17:36:00 +0100472 pass << std::string(buffer);
473 }
474 }
475
476 return pass.str();
477}
478
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100479void GUIPatternPassword::PatternDrawn()
480{
Matt Mowera8a89d12016-12-30 18:10:37 -0600481 if (!mPassVar.empty() && mConnectedDotsLen > 0)
Aleksa Saraib25a1832015-12-31 17:36:00 +0100482 DataManager::SetValue(mPassVar, GeneratePassphrase());
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100483
Matt Mowera8a89d12016-12-30 18:10:37 -0600484 if (mAction)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100485 mAction->doActions();
486}