summaryrefslogtreecommitdiffstats
path: root/gui/patternpassword.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gui/patternpassword.cpp469
1 files changed, 469 insertions, 0 deletions
diff --git a/gui/patternpassword.cpp b/gui/patternpassword.cpp
new file mode 100644
index 000000000..ab446b9f0
--- /dev/null
+++ b/gui/patternpassword.cpp
@@ -0,0 +1,469 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+#include <sstream>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "../minuitwrp/minui.h"
+#include "../twrp-functions.hpp"
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIPatternPassword::GUIPatternPassword(xml_node<>* node)
+ : GUIObject(node)
+{
+ xml_attribute<>* attr;
+ xml_node<>* child;
+
+ // 3x3 is the default.
+ mGridSize = 3;
+ mDots = new Dot[mGridSize * mGridSize];
+ mConnectedDots = new int[mGridSize * mGridSize];
+
+ ResetActiveDots();
+ mTrackingTouch = false;
+ mNeedRender = true;
+
+ ConvertStrToColor("blue", &mDotColor);
+ ConvertStrToColor("white", &mActiveDotColor);
+ ConvertStrToColor("blue", &mLineColor);
+
+ mDotImage = mActiveDotImage = NULL;
+ mDotCircle = mActiveDotCircle = NULL;
+ mDotRadius = 50;
+ mLineWidth = 35;
+
+ mAction = NULL;
+ mUpdate = 0;
+
+ if (!node)
+ return;
+
+ LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &mPlacement);
+
+ mAction = new GUIAction(node);
+
+ child = FindNode(node, "dot");
+ if(child)
+ {
+ mDotColor = LoadAttrColor(child, "color", mDotColor);
+ mActiveDotColor = LoadAttrColor(child, "activecolor", mActiveDotColor);
+ mDotRadius = LoadAttrIntScaleX(child, "radius", mDotRadius);
+
+ mDotImage = LoadAttrImage(child, "image");
+ mActiveDotImage = LoadAttrImage(child, "activeimage");
+ }
+
+ child = FindNode(node, "line");
+ if(child)
+ {
+ mLineColor = LoadAttrColor(child, "color", mLineColor);
+ mLineWidth = LoadAttrIntScaleX(child, "width", mLineWidth);
+ }
+
+ child = FindNode(node, "data");
+ if(child)
+ mPassVar = LoadAttrString(child, "name", "");
+
+ child = FindNode(node, "size");
+ if(child) {
+ mSizeVar = LoadAttrString(child, "name", "");
+
+ // Use the configured default, if set.
+ size_t size = LoadAttrInt(child, "default", mGridSize);
+ Resize(size);
+ }
+
+ if(!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource())
+ {
+ mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha);
+ mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha);
+ }
+ else
+ mDotRadius = mDotImage->GetWidth()/2;
+
+ SetRenderPos(mRenderX, mRenderY, mRenderW, mRenderH);
+}
+
+GUIPatternPassword::~GUIPatternPassword()
+{
+ delete mDotImage;
+ delete mActiveDotImage;
+ delete mAction;
+
+ delete[] mDots;
+ delete[] mConnectedDots;
+
+ if(mDotCircle)
+ gr_free_surface(mDotCircle);
+
+ if(mActiveDotCircle)
+ gr_free_surface(mActiveDotCircle);
+}
+
+void GUIPatternPassword::ResetActiveDots()
+{
+ mConnectedDotsLen = 0;
+ mCurLineX = mCurLineY = -1;
+ for(size_t i = 0; i < mGridSize * mGridSize; ++i)
+ mDots[i].active = false;
+}
+
+int GUIPatternPassword::SetRenderPos(int x, int y, int w, int h)
+{
+ mRenderX = x;
+ mRenderY = y;
+
+ if (w || h)
+ {
+ mRenderW = w;
+ mRenderH = h;
+
+ mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+ SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+ }
+
+ CalculateDotPositions();
+ return 0;
+}
+
+void GUIPatternPassword::CalculateDotPositions(void)
+{
+ const int num_gaps = mGridSize - 1;
+ const int step_x = (mRenderW - mDotRadius*2) / num_gaps;
+ const int step_y = (mRenderH - mDotRadius*2) / num_gaps;
+ int x = mRenderX;
+ int y = mRenderY;
+
+ /* Order is important for keyphrase generation:
+ *
+ * 0 1 2 3 ... n-1
+ * n n+1 n+2 n+3 ... 2n-1
+ * 2n 2n+1 2n+2 2n+3 ... 3n-1
+ * 3n 3n+1 3n+2 3n+3 ... 4n-1
+ * : : : :
+ * n*n-1
+ */
+
+ for(size_t r = 0; r < mGridSize; ++r)
+ {
+ for(size_t c = 0; c < mGridSize; ++c)
+ {
+ mDots[mGridSize*r + c].x = x;
+ mDots[mGridSize*r + c].y = y;
+ x += step_x;
+ }
+ x = mRenderX;
+ y += step_y;
+ }
+}
+
+int GUIPatternPassword::Render(void)
+{
+ if(!isConditionTrue())
+ return 0;
+
+ gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
+ for(size_t i = 1; i < mConnectedDotsLen; ++i) {
+ const Dot& dp = mDots[mConnectedDots[i-1]];
+ const Dot& dc = mDots[mConnectedDots[i]];
+ gr_line(dp.x + mDotRadius, dp.y + mDotRadius, dc.x + mDotRadius, dc.y + mDotRadius, mLineWidth);
+ }
+
+ if(mConnectedDotsLen > 0 && mTrackingTouch) {
+ const Dot& dc = mDots[mConnectedDots[mConnectedDotsLen-1]];
+ gr_line(dc.x + mDotRadius, dc.y + mDotRadius, mCurLineX, mCurLineY, mLineWidth);
+ }
+
+ for(size_t i = 0; i < mGridSize * mGridSize; ++i) {
+ if(mDotCircle) {
+ gr_blit(mDotCircle, 0, 0, gr_get_width(mDotCircle), gr_get_height(mDotCircle), mDots[i].x, mDots[i].y);
+ if(mDots[i].active) {
+ gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2);
+ }
+ } else {
+ if(mDots[i].active) {
+ gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(),
+ mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2));
+ } else {
+ gr_blit(mDotImage->GetResource(), 0, 0, mDotImage->GetWidth(), mDotImage->GetHeight(), mDots[i].x, mDots[i].y);
+ }
+ }
+ }
+ return 0;
+}
+
+int GUIPatternPassword::Update(void)
+{
+ if(!isConditionTrue())
+ return 0;
+
+ int res = mNeedRender ? 2 : 1;
+ mNeedRender = false;
+ return res;
+}
+
+void GUIPatternPassword::Resize(size_t n) {
+ if(mGridSize == n)
+ return;
+
+ delete[] mDots;
+ delete[] mConnectedDots;
+
+ mGridSize = n;
+ mDots = new Dot[n*n];
+ mConnectedDots = new int[n*n];
+
+ ResetActiveDots();
+ CalculateDotPositions();
+ mTrackingTouch = false;
+ mNeedRender = true;
+}
+
+static int pow(int x, int i)
+{
+ int result = 1;
+ if (i<0)
+ return 0;
+ while(i-- > 0)
+ result *= x;
+ return result;
+}
+
+static bool IsInCircle(int x, int y, int ox, int oy, int r)
+{
+ return pow(x - ox, 2) + pow(y - oy, 2) <= pow(r, 2);
+}
+
+int GUIPatternPassword::InDot(int x, int y)
+{
+ for(size_t i = 0; i < mGridSize * mGridSize; ++i) {
+ if(IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3))
+ return i;
+ }
+ return -1;
+}
+
+bool GUIPatternPassword::DotUsed(int dot_idx)
+{
+ for(size_t i = 0; i < mConnectedDotsLen; ++i) {
+ if(mConnectedDots[i] == dot_idx)
+ return true;
+ }
+ return false;
+}
+
+void GUIPatternPassword::ConnectDot(int dot_idx)
+{
+ if(mConnectedDotsLen >= mGridSize * mGridSize)
+ {
+ LOGERR("mConnectedDots in GUIPatternPassword has overflown!\n");
+ return;
+ }
+
+ mConnectedDots[mConnectedDotsLen++] = dot_idx;
+ mDots[dot_idx].active = true;
+}
+
+void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx)
+{
+ if(mConnectedDotsLen == 0)
+ return;
+
+ const int prev_dot_idx = mConnectedDots[mConnectedDotsLen-1];
+
+ int px = prev_dot_idx % mGridSize;
+ int py = prev_dot_idx / mGridSize;
+
+ int nx = next_dot_idx % mGridSize;
+ int ny = next_dot_idx / mGridSize;
+
+ /*
+ * We connect all dots that are in a straight line between the previous dot
+ * and the next one. This is simple for 3x3, but is more complicated for
+ * larger grids.
+ *
+ * Weirdly, Android doesn't do the logical thing when it comes to connecting
+ * dots between two points. Rather than simply adding all points that lie
+ * on the line between the start and end points, it instead only connects
+ * dots that are adjacent in only three directions -- horizontal, vertical
+ * and diagonal (45°).
+ *
+ * So we can just iterate over the correct axes, taking care to ensure that
+ * the order in which the intermediate points are added to the pattern is
+ * correct.
+ */
+
+ int x = px;
+ int y = py;
+
+ int Dx = (nx > px) ? 1 : -1;
+ int Dy = (ny > py) ? 1 : -1;
+
+ // Vertical lines.
+ if(px == nx)
+ Dx = 0;
+
+ // Horizontal lines.
+ else if(py == ny)
+ Dy = 0;
+
+ // Diagonal lines (|∆x| = |∆y|).
+ else if(abs(px - nx) == abs(py - ny))
+ ;
+
+ // No valid intermediate dots.
+ else
+ return;
+
+ // Iterate along axis, adding dots in the correct order.
+ while((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) {
+ x += Dx;
+ y += Dy;
+
+ int idx = mGridSize * y + x;
+ if(!DotUsed(idx))
+ ConnectDot(idx);
+ }
+}
+
+int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+ if(!isConditionTrue())
+ return -1;
+
+ switch (state)
+ {
+ case TOUCH_START:
+ {
+ const int dot_idx = InDot(x, y);
+ if(dot_idx == -1)
+ break;
+
+ mTrackingTouch = true;
+ ResetActiveDots();
+ ConnectDot(dot_idx);
+ DataManager::Vibrate("tw_button_vibrate");
+ mCurLineX = x;
+ mCurLineY = y;
+ mNeedRender = true;
+ break;
+ }
+ case TOUCH_DRAG:
+ {
+ if(!mTrackingTouch)
+ break;
+
+ const int dot_idx = InDot(x, y);
+ if(dot_idx != -1 && !DotUsed(dot_idx))
+ {
+ ConnectIntermediateDots(dot_idx);
+ ConnectDot(dot_idx);
+ DataManager::Vibrate("tw_button_vibrate");
+ }
+
+ mCurLineX = x;
+ mCurLineY = y;
+ mNeedRender = true;
+ break;
+ }
+ case TOUCH_RELEASE:
+ {
+ if(!mTrackingTouch)
+ break;
+
+ mNeedRender = true;
+ mTrackingTouch = false;
+ PatternDrawn();
+ ResetActiveDots();
+ break;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+int GUIPatternPassword::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+ if(!isConditionTrue())
+ return 0;
+
+ if(varName == mSizeVar) {
+ Resize(atoi(value.c_str()));
+ mUpdate = true;
+ }
+ return 0;
+}
+
+static unsigned int getSDKVersion(void) {
+ unsigned int sdkver = 23;
+ string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
+ if (!sdkverstr.empty()) {
+ sdkver = (unsigned int)strtoull(sdkverstr.c_str(), NULL, 10);
+ sdkver = (sdkver != 0) ? sdkver : 23;
+ }
+ LOGINFO("sdk version is %u\n", sdkver);
+ return sdkver;
+}
+
+std::string GUIPatternPassword::GeneratePassphrase()
+{
+ char pattern[mConnectedDotsLen];
+ for(size_t i = 0; i < mConnectedDotsLen; i++) {
+ pattern[i] = (char) mConnectedDots[i];
+ }
+
+ std::stringstream pass;
+ char buffer[3] = {0};
+
+ if ((mGridSize == 3) || (getSDKVersion() >= 23)) {
+ // Marshmallow uses a consistent method
+ for (size_t i = 0; i < mConnectedDotsLen; i++) {
+ buffer[0] = (pattern[i] & 0xff) + '1';
+ pass << std::string(buffer);
+ }
+ } else {
+ /*
+ * Okay, rant time for pre-Marshmallow ROMs.
+ * It turns out that Android and CyanogenMod have *two* separate methods
+ * for generating passphrases from patterns. This is a legacy issue, as
+ * Android only supports 3x3 grids, and so we need to support both.
+ * Luckily, CyanogenMod is in the same boat as us and needs to support
+ * Android's 3x3 encryption style.
+ *
+ * In order to generate a 3x3 passphrase, add 1 to each dot index
+ * and concatenate the string representation of the integers. No
+ * padding should be added.
+ *
+ * For *all* other NxN passphrases (until a 16x16 grid comes along),
+ * they are generated by taking "%.2x" for each dot index and
+ * concatenating the results (without adding 1).
+ */
+ for (size_t i = 0; i < mConnectedDotsLen; i++) {
+ snprintf(buffer, 3, "%.2x", pattern[i] & 0xff);
+ pass << std::string(buffer);
+ }
+ }
+
+ return pass.str();
+}
+
+void GUIPatternPassword::PatternDrawn()
+{
+ if(!mPassVar.empty() && mConnectedDotsLen > 0)
+ DataManager::SetValue(mPassVar, GeneratePassphrase());
+
+ if(mAction)
+ mAction->doActions();
+}