That fuck shit the fascists are using
1/*
2 * Copyright (C) 2011 Whisper Systems
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17package org.tm.archive;
18
19import android.animation.Animator;
20import android.app.KeyguardManager;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.PorterDuff;
24import android.os.Bundle;
25import android.text.Editable;
26import android.text.InputType;
27import android.text.SpannableString;
28import android.text.Spanned;
29import android.text.style.RelativeSizeSpan;
30import android.text.style.TypefaceSpan;
31import android.view.KeyEvent;
32import android.view.Menu;
33import android.view.MenuInflater;
34import android.view.MenuItem;
35import android.view.View;
36import android.view.View.OnClickListener;
37import android.view.animation.Animation;
38import android.view.animation.BounceInterpolator;
39import android.view.animation.TranslateAnimation;
40import android.view.inputmethod.EditorInfo;
41import android.widget.EditText;
42import android.widget.ImageButton;
43import android.widget.ImageView;
44import android.widget.TextView;
45
46import androidx.annotation.NonNull;
47import androidx.appcompat.widget.Toolbar;
48import androidx.biometric.BiometricManager;
49import androidx.biometric.BiometricPrompt;
50
51import org.signal.core.util.ThreadUtil;
52import org.signal.core.util.logging.Log;
53import org.tm.archive.animation.AnimationCompleteListener;
54import org.tm.archive.components.AnimatingToggle;
55import org.tm.archive.crypto.InvalidPassphraseException;
56import org.tm.archive.crypto.MasterSecret;
57import org.tm.archive.crypto.MasterSecretUtil;
58import org.tm.archive.logsubmit.SubmitDebugLogActivity;
59import org.tm.archive.util.CommunicationActions;
60import org.tm.archive.util.DynamicIntroTheme;
61import org.tm.archive.util.DynamicLanguage;
62import org.tm.archive.util.SupportEmailUtil;
63import org.tm.archive.util.TextSecurePreferences;
64
65import kotlin.Unit;
66
67/**
68 * Activity that prompts for a user's passphrase.
69 *
70 * @author Moxie Marlinspike
71 */
72public class PassphrasePromptActivity extends PassphraseActivity {
73
74 private static final String TAG = Log.tag(PassphrasePromptActivity.class);
75 private static final short AUTHENTICATE_REQUEST_CODE = 1007;
76 private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
77 public static final String FROM_FOREGROUND = "from_foreground";
78
79 private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
80 private DynamicLanguage dynamicLanguage = new DynamicLanguage();
81
82 private View passphraseAuthContainer;
83 private ImageView fingerprintPrompt;
84 private TextView lockScreenButton;
85
86 private EditText passphraseText;
87 private ImageButton showButton;
88 private ImageButton hideButton;
89 private AnimatingToggle visibilityToggle;
90
91 private BiometricManager biometricManager;
92 private BiometricPrompt biometricPrompt;
93 private BiometricDeviceAuthentication biometricAuth;
94
95 private boolean authenticated;
96 private boolean hadFailure;
97 private boolean alreadyShown;
98
99 private final Runnable resumeScreenLockRunnable = () -> {
100 resumeScreenLock(!alreadyShown);
101 alreadyShown = true;
102 };
103
104 @Override
105 public void onCreate(Bundle savedInstanceState) {
106 Log.i(TAG, "onCreate()");
107 dynamicTheme.onCreate(this);
108 dynamicLanguage.onCreate(this);
109 super.onCreate(savedInstanceState);
110
111 setContentView(R.layout.prompt_passphrase_activity);
112 initializeResources();
113
114 alreadyShown = (savedInstanceState != null && savedInstanceState.getBoolean(BUNDLE_ALREADY_SHOWN)) ||
115 getIntent().getBooleanExtra(FROM_FOREGROUND, false);
116 }
117
118 @Override
119 protected void onSaveInstanceState(@NonNull Bundle outState) {
120 super.onSaveInstanceState(outState);
121 outState.putBoolean(BUNDLE_ALREADY_SHOWN, alreadyShown);
122 }
123
124 @Override
125 public void onResume() {
126 super.onResume();
127 dynamicTheme.onResume(this);
128 dynamicLanguage.onResume(this);
129
130 setLockTypeVisibility();
131
132 if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !hadFailure) {
133 ThreadUtil.postToMain(resumeScreenLockRunnable);
134 }
135
136 hadFailure = false;
137
138 fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
139 fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_accent_primary), PorterDuff.Mode.SRC_IN);
140 }
141
142 @Override
143 public void onPause() {
144 super.onPause();
145 ThreadUtil.cancelRunnableOnMain(resumeScreenLockRunnable);
146 biometricPrompt.cancelAuthentication();
147 }
148
149 @Override
150 protected void onNewIntent(Intent intent) {
151 super.onNewIntent(intent);
152 setIntent(intent);
153 }
154
155 @Override
156 public boolean onCreateOptionsMenu(Menu menu) {
157 MenuInflater inflater = this.getMenuInflater();
158 menu.clear();
159
160 inflater.inflate(R.menu.passphrase_prompt, menu);
161
162 super.onCreateOptionsMenu(menu);
163 return true;
164 }
165
166 @Override
167 public boolean onOptionsItemSelected(MenuItem item) {
168 super.onOptionsItemSelected(item);
169 if (item.getItemId() == R.id.menu_submit_debug_logs) {
170 handleLogSubmit();
171 return true;
172 } else if (item.getItemId() == R.id.menu_contact_support) {
173 sendEmailToSupport();
174 return true;
175 }
176
177 return false;
178 }
179
180 @Override
181 public void onActivityResult(int requestCode, int resultCode, Intent data) {
182 super.onActivityResult(requestCode, resultCode, data);
183
184 if (requestCode != AUTHENTICATE_REQUEST_CODE) return;
185
186 if (resultCode == RESULT_OK) {
187 handleAuthenticated();
188 } else {
189 Log.w(TAG, "Authentication failed");
190 hadFailure = true;
191 }
192 }
193
194 private void handleLogSubmit() {
195 Intent intent = new Intent(this, SubmitDebugLogActivity.class);
196 startActivity(intent);
197 }
198
199 private void handlePassphrase() {
200 try {
201 Editable text = passphraseText.getText();
202 String passphrase = (text == null ? "" : text.toString());
203 MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, passphrase);
204
205 setMasterSecret(masterSecret);
206 } catch (InvalidPassphraseException ipe) {
207 passphraseText.setText("");
208 passphraseText.setError(
209 getString(R.string.PassphrasePromptActivity_invalid_passphrase_exclamation));
210 }
211 }
212
213 private void handleAuthenticated() {
214 try {
215 authenticated = true;
216
217 MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
218 setMasterSecret(masterSecret);
219 } catch (InvalidPassphraseException e) {
220 throw new AssertionError(e);
221 }
222 }
223
224 private void setPassphraseVisibility(boolean visibility) {
225 int cursorPosition = passphraseText.getSelectionStart();
226 if (visibility) {
227 passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
228 InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
229 } else {
230 passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
231 InputType.TYPE_TEXT_VARIATION_PASSWORD);
232 }
233 passphraseText.setSelection(cursorPosition);
234 }
235
236 private void initializeResources() {
237
238 ImageButton okButton = findViewById(R.id.ok_button);
239 Toolbar toolbar = findViewById(R.id.toolbar);
240
241 showButton = findViewById(R.id.passphrase_visibility);
242 hideButton = findViewById(R.id.passphrase_visibility_off);
243 visibilityToggle = findViewById(R.id.button_toggle);
244 passphraseText = findViewById(R.id.passphrase_edit);
245 passphraseAuthContainer = findViewById(R.id.password_auth_container);
246 fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
247 lockScreenButton = findViewById(R.id.lock_screen_auth_container);
248 biometricManager = BiometricManager.from(this);
249 biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
250 BiometricPrompt.PromptInfo biometricPromptInfo = new BiometricPrompt.PromptInfo
251 .Builder()
252 .setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
253 .setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
254 .build();
255 biometricAuth = new BiometricDeviceAuthentication(biometricManager, biometricPrompt, biometricPromptInfo);
256 setSupportActionBar(toolbar);
257 getSupportActionBar().setTitle("");
258
259 SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase));
260 hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
261 hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
262
263 passphraseText.setHint(hint);
264 okButton.setOnClickListener(new OkButtonClickListener());
265 showButton.setOnClickListener(new ShowButtonOnClickListener());
266 hideButton.setOnClickListener(new HideButtonOnClickListener());
267 passphraseText.setOnEditorActionListener(new PassphraseActionListener());
268 passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock),
269 EditorInfo.IME_ACTION_DONE);
270
271 fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
272 fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
273
274 lockScreenButton.setOnClickListener(v -> resumeScreenLock(true));
275 }
276
277 private void setLockTypeVisibility() {
278 if (TextSecurePreferences.isScreenLockEnabled(this)) {
279 passphraseAuthContainer.setVisibility(View.GONE);
280 fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BiometricDeviceAuthentication.BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
281 : View.GONE);
282 lockScreenButton.setVisibility(View.VISIBLE);
283 } else {
284 passphraseAuthContainer.setVisibility(View.VISIBLE);
285 fingerprintPrompt.setVisibility(View.GONE);
286 lockScreenButton.setVisibility(View.GONE);
287 }
288 }
289
290 private void resumeScreenLock(boolean force) {
291 if (!biometricAuth.authenticate(getApplicationContext(), force, this::showConfirmDeviceCredentialIntent)) {
292 handleAuthenticated();
293 }
294 }
295
296 private void sendEmailToSupport() {
297 String body = SupportEmailUtil.generateSupportEmailBody(this,
298 R.string.PassphrasePromptActivity_signal_android_lock_screen,
299 null,
300 null);
301 CommunicationActions.openEmail(this,
302 SupportEmailUtil.getSupportEmailAddress(this),
303 getString(R.string.PassphrasePromptActivity_signal_android_lock_screen),
304 body);
305 }
306
307 public Unit showConfirmDeviceCredentialIntent() {
308 KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
309 Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
310
311 startActivityForResult(intent, AUTHENTICATE_REQUEST_CODE);
312 return Unit.INSTANCE;
313 }
314
315 private class PassphraseActionListener implements TextView.OnEditorActionListener {
316 @Override
317 public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
318 if ((keyEvent == null && actionId == EditorInfo.IME_ACTION_DONE) ||
319 (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
320 (actionId == EditorInfo.IME_NULL)))
321 {
322 handlePassphrase();
323 return true;
324 } else if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_UP &&
325 actionId == EditorInfo.IME_NULL)
326 {
327 return true;
328 }
329
330 return false;
331 }
332 }
333
334 private class OkButtonClickListener implements OnClickListener {
335 @Override
336 public void onClick(View v) {
337 handlePassphrase();
338 }
339 }
340
341 private class ShowButtonOnClickListener implements OnClickListener {
342 @Override
343 public void onClick(View v) {
344 visibilityToggle.display(hideButton);
345 setPassphraseVisibility(true);
346 }
347 }
348
349 private class HideButtonOnClickListener implements OnClickListener {
350 @Override
351 public void onClick(View v) {
352 visibilityToggle.display(showButton);
353 setPassphraseVisibility(false);
354 }
355 }
356
357 @Override
358 protected void cleanup() {
359 this.passphraseText.setText("");
360 System.gc();
361 }
362
363 private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
364 @Override
365 public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
366 Log.w(TAG, "Authentication error: " + errorCode);
367 hadFailure = true;
368
369 if (errorCode != BiometricPrompt.ERROR_CANCELED && errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
370 onAuthenticationFailed();
371 }
372 }
373
374 @Override
375 public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
376 Log.i(TAG, "onAuthenticationSucceeded");
377 fingerprintPrompt.setImageResource(R.drawable.symbol_check_white_48);
378 fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
379 fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
380 @Override
381 public void onAnimationEnd(Animator animation) {
382 handleAuthenticated();
383 }
384 }).start();
385 }
386
387 @Override
388 public void onAuthenticationFailed() {
389 Log.w(TAG, "onAuthenticationFailed()");
390
391 fingerprintPrompt.setImageResource(R.drawable.symbol_x_white_48);
392 fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
393
394 TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
395 shake.setDuration(50);
396 shake.setRepeatCount(7);
397 shake.setAnimationListener(new Animation.AnimationListener() {
398 @Override
399 public void onAnimationStart(Animation animation) {}
400
401 @Override
402 public void onAnimationEnd(Animation animation) {
403 fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
404 fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_accent_primary), PorterDuff.Mode.SRC_IN);
405 }
406
407 @Override
408 public void onAnimationRepeat(Animation animation) {}
409 });
410
411 fingerprintPrompt.startAnimation(shake);
412 }
413 }
414}