/* Copyright 2017 TeamWin This file is part of TWRP/TeamWin Recovery Project. TWRP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TWRP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TWRP. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include 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_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 if (mDotImage && mDotImage->GetResource()) 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 && mActiveDotImage && mActiveDotImage->GetResource()) { 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 if (mDotImage && mDotImage->GetResource()) { 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); #ifndef TW_NO_HAPTICS DataManager::Vibrate("tw_button_vibrate"); #endif 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); #ifndef TW_NO_HAPTICS DataManager::Vibrate("tw_button_vibrate"); #endif } 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(); }