Skip to content

Commit 742a9b9

Browse files
committed
Added: PasswordAPI
this is an alternate to termux-fingerprint that let's you to use your PIN/Pattern/Password instead of hardware fingerprint sensor
1 parent 15d5fb6 commit 742a9b9

File tree

3 files changed

+210
-1
lines changed

3 files changed

+210
-1
lines changed

app/src/main/AndroidManifest.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,12 @@
127127
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
128128
android:resource="@xml/nfc_tech_filter" />
129129

130-
</activity>
130+
</activity>
131+
132+
<activity android:name=".apis.PasswordAPI$PasswordActivity"
133+
android:theme="@style/TransparentTheme"
134+
android:excludeFromRecents="true"
135+
android:exported="false" />
131136

132137
<activity android:name=".apis.SAFAPI$SAFActivity"
133138
android:theme="@style/TransparentTheme"

app/src/main/java/com/termux/api/TermuxApiReceiver.java

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.termux.api.apis.NfcAPI;
3030
import com.termux.api.apis.NotificationAPI;
3131
import com.termux.api.apis.NotificationListAPI;
32+
import com.termux.api.apis.PasswordAPI;
3233
import com.termux.api.apis.SAFAPI;
3334
import com.termux.api.apis.SensorAPI;
3435
import com.termux.api.apis.ShareAPI;
@@ -190,6 +191,9 @@ private void doWork(Context context, Intent intent) {
190191
case "NotificationReply":
191192
NotificationAPI.onReceiveReplyToNotification(this, context, intent);
192193
break;
194+
case "Password":
195+
PasswordAPI.onReceive(context, intent);
196+
break;
193197
case "SAF":
194198
SAFAPI.onReceive(this, context, intent);
195199
break;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package com.termux.api.apis;
2+
3+
import android.app.KeyguardManager;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.os.Bundle;
7+
import android.os.Handler;
8+
import android.os.Looper;
9+
import android.util.JsonWriter;
10+
11+
import androidx.annotation.Nullable;
12+
import androidx.fragment.app.FragmentActivity;
13+
14+
import com.termux.api.util.ResultReturner;
15+
import com.termux.shared.logger.Logger;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
import static android.app.Activity.RESULT_OK;
21+
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
22+
23+
/**
24+
* This API allows users to use device password prompt (PIN/Pattern/Password) as an authentication mechanism.
25+
*/
26+
public class PasswordAPI {
27+
28+
protected static final String TAG = "PasswordAPI";
29+
protected static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1;
30+
// milliseconds to wait before canceling the prompt
31+
protected static final int PROMPT_TIMEOUT = 10000;
32+
33+
// error constants
34+
protected static final String ERROR_NO_PASSWORD = "ERROR_NO_PASSWORD";
35+
protected static final String ERROR_TIMEOUT = "ERROR_TIMEOUT";
36+
37+
// password authentication result constants
38+
protected static final String AUTH_RESULT_SUCCESS = "AUTH_RESULT_SUCCESS";
39+
protected static final String AUTH_RESULT_FAILURE = "AUTH_RESULT_FAILURE";
40+
protected static final String AUTH_RESULT_UNKNOWN = "AUTH_RESULT_UNKNOWN";
41+
42+
// store result of password initialization / authentication
43+
protected static PasswordResult passwordResult = new PasswordResult();
44+
45+
// have we posted our result back?
46+
protected static boolean postedResult = false;
47+
48+
/**
49+
* Handles setup of the password prompt and writes result to the console.
50+
*/
51+
public static void onReceive(final Context context, final Intent intent) {
52+
Logger.logDebug(TAG, "onReceive");
53+
54+
resetPasswordResult();
55+
56+
if (validatePasswordPrompt(context)) {
57+
Intent passwordIntent = new Intent(context, PasswordActivity.class);
58+
passwordIntent.putExtras(intent.getExtras());
59+
passwordIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
60+
context.startActivity(passwordIntent);
61+
} else {
62+
postPasswordResult(context, intent, passwordResult);
63+
}
64+
}
65+
66+
/**
67+
* Writes the result of our password authentication to the console.
68+
*/
69+
protected static void postPasswordResult(Context context, Intent intent, final PasswordResult result) {
70+
ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() {
71+
@Override
72+
public void writeJson(JsonWriter out) throws Exception {
73+
out.beginObject();
74+
75+
out.name("errors");
76+
out.beginArray();
77+
for (String error : result.errors) {
78+
out.value(error);
79+
}
80+
out.endArray();
81+
82+
out.name("auth_result").value(result.authResult);
83+
out.endObject();
84+
85+
out.flush();
86+
out.close();
87+
postedResult = true;
88+
}
89+
});
90+
}
91+
92+
/**
93+
* Ensure that the device is secured with a password (PIN/Pattern/Password).
94+
*/
95+
protected static boolean validatePasswordPrompt(Context context) {
96+
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
97+
boolean result = true;
98+
99+
if (keyguardManager == null || !keyguardManager.isDeviceSecure()) {
100+
appendPasswordError(ERROR_NO_PASSWORD);
101+
result = false;
102+
}
103+
return result;
104+
}
105+
106+
/**
107+
* Activity that handles prompting the user for their device credentials.
108+
*/
109+
public static class PasswordActivity extends FragmentActivity {
110+
111+
private static final String TAG = "PasswordActivity";
112+
113+
@Override
114+
public void onCreate(Bundle savedInstanceState) {
115+
Logger.logDebug(TAG, "onCreate");
116+
super.onCreate(savedInstanceState);
117+
handlePasswordPrompt();
118+
}
119+
120+
/**
121+
* Handle setup and display of the password prompt.
122+
*/
123+
protected void handlePasswordPrompt() {
124+
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
125+
String title = getIntent().hasExtra("title") ? getIntent().getStringExtra("title") : "Authenticate";
126+
String description = getIntent().hasExtra("description") ? getIntent().getStringExtra("description") : "";
127+
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(title, description);
128+
129+
if (intent != null) {
130+
startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
131+
addPromptTimeout();
132+
} else {
133+
// No credential is set (should not happen due to prior validation)
134+
appendPasswordError(ERROR_NO_PASSWORD);
135+
setAuthResult(AUTH_RESULT_FAILURE);
136+
postPasswordResult(PasswordActivity.this, getIntent(), passwordResult);
137+
finish();
138+
}
139+
}
140+
141+
@Override
142+
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
143+
super.onActivityResult(requestCode, resultCode, data);
144+
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
145+
if (resultCode == RESULT_OK) {
146+
setAuthResult(AUTH_RESULT_SUCCESS);
147+
} else {
148+
setAuthResult(AUTH_RESULT_FAILURE);
149+
}
150+
postPasswordResult(PasswordActivity.this, getIntent(), passwordResult);
151+
finish();
152+
}
153+
}
154+
155+
/**
156+
* Adds a timeout for the password prompt which will force a result return if we haven't already received one.
157+
*/
158+
protected void addPromptTimeout() {
159+
final Handler timeoutHandler = new Handler(Looper.getMainLooper());
160+
timeoutHandler.postDelayed(() -> {
161+
if (!postedResult) {
162+
appendPasswordError(ERROR_TIMEOUT);
163+
setAuthResult(AUTH_RESULT_FAILURE);
164+
postPasswordResult(PasswordActivity.this, getIntent(), passwordResult);
165+
finish();
166+
}
167+
}, PROMPT_TIMEOUT);
168+
}
169+
}
170+
171+
/**
172+
* Clear out previous password authentication result.
173+
*/
174+
protected static void resetPasswordResult() {
175+
passwordResult = new PasswordResult();
176+
postedResult = false;
177+
}
178+
179+
/**
180+
* Add an error to our password result.
181+
*/
182+
protected static void appendPasswordError(String error) {
183+
passwordResult.errors.add(error);
184+
}
185+
186+
/**
187+
* Set the final result of our authentication.
188+
*/
189+
protected static void setAuthResult(String authResult) {
190+
passwordResult.authResult = authResult;
191+
}
192+
193+
/**
194+
* Simple class to encapsulate information about the result of a password authentication attempt.
195+
*/
196+
static class PasswordResult {
197+
public String authResult = AUTH_RESULT_UNKNOWN;
198+
public List<String> errors = new ArrayList<>();
199+
}
200+
}

0 commit comments

Comments
 (0)