| /* |
| 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #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_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(); |
| } |