blob: a6ab83128e9964feddb162b7d32d23898f6491a1 [file] [log] [blame]
Vojtech Bocek7e11ac52015-03-05 23:21:49 +01001#include <stdarg.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <fcntl.h>
6#include <sys/types.h>
7#include <time.h>
8#include <unistd.h>
9#include <stdlib.h>
10
11#include <string>
Aleksa Saraib25a1832015-12-31 17:36:00 +010012#include <sstream>
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010013
14extern "C" {
15#include "../twcommon.h"
16#include "../minuitwrp/minui.h"
17}
18
19#include "rapidxml.hpp"
20#include "objects.hpp"
21
22GUIPatternPassword::GUIPatternPassword(xml_node<>* node)
23 : GUIObject(node)
24{
25 xml_attribute<>* attr;
26 xml_node<>* child;
27
Aleksa Saraib25a1832015-12-31 17:36:00 +010028 // 3x3 is the default.
29 mGridSize = 3;
30 mDots = new Dot[mGridSize * mGridSize];
31 mConnectedDots = new int[mGridSize * mGridSize];
32
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010033 ResetActiveDots();
34 mTrackingTouch = false;
35 mNeedRender = true;
36
37 ConvertStrToColor("blue", &mDotColor);
38 ConvertStrToColor("white", &mActiveDotColor);
39 ConvertStrToColor("blue", &mLineColor);
40
41 mDotImage = mActiveDotImage = NULL;
42 mDotCircle = mActiveDotCircle = NULL;
43 mDotRadius = 50;
44 mLineWidth = 35;
45
46 mAction = NULL;
47 mUpdate = 0;
48
49 if (!node)
50 return;
51
52 LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &mPlacement);
53
54 mAction = new GUIAction(node);
55
56 child = FindNode(node, "dot");
57 if(child)
58 {
59 mDotColor = LoadAttrColor(child, "color", mDotColor);
60 mActiveDotColor = LoadAttrColor(child, "activecolor", mActiveDotColor);
61 mDotRadius = LoadAttrIntScaleX(child, "radius", mDotRadius);
62
63 mDotImage = LoadAttrImage(child, "image");
64 mActiveDotImage = LoadAttrImage(child, "activeimage");
65 }
66
67 child = FindNode(node, "line");
68 if(child)
69 {
70 mLineColor = LoadAttrColor(child, "color", mLineColor);
71 mLineWidth = LoadAttrIntScaleX(child, "width", mLineWidth);
72 }
73
74 child = FindNode(node, "data");
75 if(child)
76 mPassVar = LoadAttrString(child, "name", "");
77
Aleksa Saraib25a1832015-12-31 17:36:00 +010078 child = FindNode(node, "size");
79 if(child) {
80 mSizeVar = LoadAttrString(child, "name", "");
81
82 // Use the configured default, if set.
83 size_t size = LoadAttrInt(child, "default", mGridSize);
84 Resize(size);
85 }
Vojtech Bocek7e11ac52015-03-05 23:21:49 +010086
87 if(!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource())
88 {
89 mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha);
90 mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha);
91 }
92 else
93 mDotRadius = mDotImage->GetWidth()/2;
94
95 SetRenderPos(mRenderX, mRenderY, mRenderW, mRenderH);
96}
97
98GUIPatternPassword::~GUIPatternPassword()
99{
100 delete mDotImage;
101 delete mActiveDotImage;
102 delete mAction;
103
Aleksa Saraib25a1832015-12-31 17:36:00 +0100104 delete[] mDots;
105 delete[] mConnectedDots;
106
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100107 if(mDotCircle)
108 gr_free_surface(mDotCircle);
109
110 if(mActiveDotCircle)
111 gr_free_surface(mActiveDotCircle);
112}
113
114void GUIPatternPassword::ResetActiveDots()
115{
116 mConnectedDotsLen = 0;
117 mCurLineX = mCurLineY = -1;
Aleksa Saraib25a1832015-12-31 17:36:00 +0100118 for(size_t i = 0; i < mGridSize * mGridSize; ++i)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100119 mDots[i].active = false;
120}
121
122int GUIPatternPassword::SetRenderPos(int x, int y, int w, int h)
123{
124 mRenderX = x;
125 mRenderY = y;
126
127 if (w || h)
128 {
129 mRenderW = w;
130 mRenderH = h;
131
132 mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
133 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
134 }
135
136 CalculateDotPositions();
137 return 0;
138}
139
140void GUIPatternPassword::CalculateDotPositions(void)
141{
Aleksa Saraib25a1832015-12-31 17:36:00 +0100142 const int num_gaps = mGridSize - 1;
143 const int step_x = (mRenderW - mDotRadius*2) / num_gaps;
144 const int step_y = (mRenderH - mDotRadius*2) / num_gaps;
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100145 int x = mRenderX;
146 int y = mRenderY;
147
Aleksa Saraib25a1832015-12-31 17:36:00 +0100148 /* Order is important for keyphrase generation:
149 *
150 * 0 1 2 3 ... n-1
151 * n n+1 n+2 n+3 ... 2n-1
152 * 2n 2n+1 2n+2 2n+3 ... 3n-1
153 * 3n 3n+1 3n+2 3n+3 ... 4n-1
154 * : : : :
155 * n*n-1
156 */
157
158 for(size_t r = 0; r < mGridSize; ++r)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100159 {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100160 for(size_t c = 0; c < mGridSize; ++c)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100161 {
Aleksa Saraib25a1832015-12-31 17:36:00 +0100162 mDots[mGridSize*r + c].x = x;
163 mDots[mGridSize*r + c].y = y;
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100164 x += step_x;
165 }
166 x = mRenderX;
167 y += step_y;
168 }
169}
170
171int GUIPatternPassword::Render(void)
172{
173 if(!isConditionTrue())
174 return 0;
175
176 gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
177 for(size_t i = 1; i < mConnectedDotsLen; ++i) {
178 const Dot& dp = mDots[mConnectedDots[i-1]];
179 const Dot& dc = mDots[mConnectedDots[i]];
180 gr_line(dp.x + mDotRadius, dp.y + mDotRadius, dc.x + mDotRadius, dc.y + mDotRadius, mLineWidth);
181 }
182
183 if(mConnectedDotsLen > 0 && mTrackingTouch) {
184 const Dot& dc = mDots[mConnectedDots[mConnectedDotsLen-1]];
185 gr_line(dc.x + mDotRadius, dc.y + mDotRadius, mCurLineX, mCurLineY, mLineWidth);
186 }
187
Aleksa Saraib25a1832015-12-31 17:36:00 +0100188 for(size_t i = 0; i < mGridSize * mGridSize; ++i) {
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100189 if(mDotCircle) {
190 gr_blit(mDotCircle, 0, 0, gr_get_width(mDotCircle), gr_get_height(mDotCircle), mDots[i].x, mDots[i].y);
191 if(mDots[i].active) {
192 gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2);
193 }
194 } else {
195 if(mDots[i].active) {
196 gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(),
197 mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2));
198 } else {
199 gr_blit(mDotImage->GetResource(), 0, 0, mDotImage->GetWidth(), mDotImage->GetHeight(), mDots[i].x, mDots[i].y);
200 }
201 }
202 }
203 return 0;
204}
205
206int GUIPatternPassword::Update(void)
207{
208 if(!isConditionTrue())
209 return 0;
210
211 int res = mNeedRender ? 2 : 1;
212 mNeedRender = false;
213 return res;
214}
215
Aleksa Saraib25a1832015-12-31 17:36:00 +0100216void GUIPatternPassword::Resize(size_t n) {
217 if(mGridSize == n)
218 return;
219
220 delete[] mDots;
221 delete[] mConnectedDots;
222
223 mGridSize = n;
224 mDots = new Dot[n*n];
225 mConnectedDots = new int[n*n];
226
227 ResetActiveDots();
228 CalculateDotPositions();
229 mTrackingTouch = false;
230 mNeedRender = true;
231}
232
233static int pow(int x, int i)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100234{
Aleksa Saraib25a1832015-12-31 17:36:00 +0100235 while(i-- > 1)
236 x *= x;
237 return x;
238}
239
240static bool IsInCircle(int x, int y, int ox, int oy, int r)
241{
242 return pow(x - ox, 2) + pow(y - oy, 2) <= pow(r, 2);
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100243}
244
245int GUIPatternPassword::InDot(int x, int y)
246{
Aleksa Saraib25a1832015-12-31 17:36:00 +0100247 for(size_t i = 0; i < mGridSize * mGridSize; ++i) {
248 if(IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3))
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100249 return i;
250 }
251 return -1;
252}
253
254bool GUIPatternPassword::DotUsed(int dot_idx)
255{
256 for(size_t i = 0; i < mConnectedDotsLen; ++i) {
257 if(mConnectedDots[i] == dot_idx)
258 return true;
259 }
260 return false;
261}
262
263void GUIPatternPassword::ConnectDot(int dot_idx)
264{
Aleksa Saraib25a1832015-12-31 17:36:00 +0100265 if(mConnectedDotsLen >= mGridSize * mGridSize)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100266 {
267 LOGERR("mConnectedDots in GUIPatternPassword has overflown!\n");
268 return;
269 }
270
271 mConnectedDots[mConnectedDotsLen++] = dot_idx;
272 mDots[dot_idx].active = true;
273}
274
Aleksa Saraib25a1832015-12-31 17:36:00 +0100275void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx)
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100276{
277 if(mConnectedDotsLen == 0)
278 return;
279
Aleksa Saraib25a1832015-12-31 17:36:00 +0100280 const int prev_dot_idx = mConnectedDots[mConnectedDotsLen-1];
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100281
Aleksa Saraib25a1832015-12-31 17:36:00 +0100282 int px = prev_dot_idx % mGridSize;
283 int py = prev_dot_idx / mGridSize;
284
285 int nx = next_dot_idx % mGridSize;
286 int ny = next_dot_idx / mGridSize;
287
288 /*
289 * We connect all dots that are in a straight line between the previous dot
290 * and the next one. This is simple for 3x3, but is more complicated for
291 * larger grids.
292 *
293 * Weirdly, Android doesn't do the logical thing when it comes to connecting
294 * dots between two points. Rather than simply adding all points that lie
295 * on the line between the start and end points, it instead only connects
296 * dots that are adjacent in only three directions -- horizontal, vertical
297 * and diagonal (45°).
298 *
299 * So we can just iterate over the correct axes, taking care to ensure that
300 * the order in which the intermediate points are added to the pattern is
301 * correct.
302 */
303
304 int x = px;
305 int y = py;
306
307 int Dx = (nx > px) ? 1 : -1;
308 int Dy = (ny > py) ? 1 : -1;
309
310 // Vertical lines.
311 if(px == nx)
312 Dx = 0;
313
314 // Horizontal lines.
315 else if(py == ny)
316 Dy = 0;
317
318 // Diagonal lines (|∆x| = |∆y|).
319 else if(abs(px - nx) == abs(py - ny))
320 ;
321
322 // No valid intermediate dots.
323 else
Vojtech Boceke8c39272015-03-15 15:25:37 +0100324 return;
Vojtech Boceke8c39272015-03-15 15:25:37 +0100325
Aleksa Saraib25a1832015-12-31 17:36:00 +0100326 // Iterate along axis, adding dots in the correct order.
327 while((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) {
328 x += Dx;
329 y += Dy;
330
331 int idx = mGridSize * y + x;
332 if(!DotUsed(idx))
333 ConnectDot(idx);
334 }
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100335}
336
337int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y)
338{
339 if(!isConditionTrue())
340 return -1;
341
342 switch (state)
343 {
344 case TOUCH_START:
345 {
346 const int dot_idx = InDot(x, y);
347 if(dot_idx == -1)
348 break;
349
350 mTrackingTouch = true;
351 ResetActiveDots();
352 ConnectDot(dot_idx);
353 DataManager::Vibrate("tw_button_vibrate");
354 mCurLineX = x;
355 mCurLineY = y;
356 mNeedRender = true;
357 break;
358 }
359 case TOUCH_DRAG:
360 {
361 if(!mTrackingTouch)
362 break;
363
364 const int dot_idx = InDot(x, y);
365 if(dot_idx != -1 && !DotUsed(dot_idx))
366 {
367 ConnectIntermediateDots(dot_idx);
368 ConnectDot(dot_idx);
369 DataManager::Vibrate("tw_button_vibrate");
370 }
371
372 mCurLineX = x;
373 mCurLineY = y;
374 mNeedRender = true;
375 break;
376 }
377 case TOUCH_RELEASE:
378 {
379 if(!mTrackingTouch)
380 break;
381
382 mNeedRender = true;
383 mTrackingTouch = false;
384 PatternDrawn();
385 ResetActiveDots();
386 break;
387 }
388 default:
389 break;
390 }
391 return 0;
392}
393
Aleksa Saraib25a1832015-12-31 17:36:00 +0100394int GUIPatternPassword::NotifyVarChange(const std::string& varName, const std::string& value)
395{
396 if(!isConditionTrue())
397 return 0;
398
399 if(varName == mSizeVar) {
400 Resize(atoi(value.c_str()));
401 mUpdate = true;
402 }
403 return 0;
404}
405
406std::string GUIPatternPassword::GeneratePassphrase()
407{
408 char pattern[mConnectedDotsLen];
409 for(size_t i = 0; i < mConnectedDotsLen; i++) {
410 pattern[i] = (char) mConnectedDots[i];
411 }
412
413 std::stringstream pass;
414
415 for(size_t i = 0; i < mConnectedDotsLen; i++) {
416 int digit = pattern[i] & 0xff;
417
418 /*
419 * Okay, rant time.
420 * It turns out that Android and CyanogenMod have *two* separate methods
421 * for generating passphrases from patterns. This is a legacy issue, as
422 * Android only supports 3x3 grids, and so we need to support both.
423 * Luckily, CyanogenMod is in the same boat as us and needs to support
424 * Android's 3x3 encryption style.
425 *
426 * In order to generate a 3x3 passphrase, add 1 to each dot index
427 * and concatenate the string representation of the integers. No
428 * padding should be added.
429 *
430 * For *all* other NxN passphrases (until a 16x16 grid comes along),
431 * they are generated by taking "%.2x" for each dot index and
432 * concatenating the results (without adding 1).
433 */
434
435 if(mGridSize == 3)
436 // Android (legacy) 3x3 grids.
437 pass << digit + 1;
438 else {
439 // Other NxN grids.
440 char buffer[3];
441 snprintf(buffer, 3, "%.2x", digit);
442 pass << std::string(buffer);
443 }
444 }
445
446 return pass.str();
447}
448
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100449void GUIPatternPassword::PatternDrawn()
450{
451 if(!mPassVar.empty() && mConnectedDotsLen > 0)
Aleksa Saraib25a1832015-12-31 17:36:00 +0100452 DataManager::SetValue(mPassVar, GeneratePassphrase());
Vojtech Bocek7e11ac52015-03-05 23:21:49 +0100453
454 if(mAction)
455 mAction->doActions();
456}