2 * Copyright (C) 2014 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.cyanogenmod.filemanager.console.secure;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.DialogInterface.OnCancelListener;
24 import android.content.DialogInterface.OnClickListener;
25 import android.content.DialogInterface.OnDismissListener;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.text.Editable;
32 import android.text.TextWatcher;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.Button;
37 import android.widget.EditText;
38 import android.widget.TextView;
40 import com.cyanogenmod.filemanager.FileManagerApplication;
41 import com.cyanogenmod.filemanager.R;
42 import com.cyanogenmod.filemanager.ui.ThemeManager;
43 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
44 import com.cyanogenmod.filemanager.util.DialogHelper;
46 import de.schlichtherle.truezip.crypto.raes.param.AesCipherParameters;
47 import de.schlichtherle.truezip.crypto.raes.Type0RaesParameters.KeyStrength;
48 import de.schlichtherle.truezip.key.KeyPromptingCancelledException;
49 import de.schlichtherle.truezip.key.KeyPromptingInterruptedException;
50 import de.schlichtherle.truezip.key.PromptingKeyProvider.Controller;
51 import de.schlichtherle.truezip.key.UnknownKeyException;
54 * A class that remembers all the secure storage
56 public class SecureStorageKeyPromptDialog
57 implements de.schlichtherle.truezip.key.PromptingKeyProvider.View<AesCipherParameters> {
59 private static final int MIN_PASSWORD_LENGTH = 8;
61 private static final int MSG_REQUEST_UNLOCK_DIALOG = 1;
63 private static boolean sResetInProgress;
64 private static boolean sDeleteInProgress;
65 private static transient AesCipherParameters sOldUnlockKey = null;
66 private static transient AesCipherParameters sUnlockKey = null;
67 private static transient AesCipherParameters sOldUnlockKeyTemp = null;
68 private static transient AesCipherParameters sUnlockKeyTemp = null;
69 private static final Object WAIT_SYNC = new Object();
72 * An activity that simulates a dialog over the activity that requested the key prompt.
74 public static class SecureStorageKeyPromptActivity extends Activity
75 implements OnClickListener, TextWatcher {
77 private AlertDialog mDialog;
79 private TextView mMessage;
80 private EditText mOldKey;
81 private EditText mKey;
82 private EditText mRepeatKey;
83 private TextView mValidationMsg;
84 private Button mUnlock;
86 private boolean mNewStorage;
87 private boolean mResetPassword;
88 private boolean mDeleteStorage;
90 AesCipherParameters mOldKeyParams;
91 AesCipherParameters mKeyParams;
94 @SuppressWarnings("deprecation")
95 protected void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
98 // Check with java.io.File instead of TFile because TFile#exists() will
99 // check for password key, which is currently locked
100 mNewStorage = !SecureConsole.getSecureStorageRoot().getFile().exists();
101 mResetPassword = sResetInProgress;
102 mDeleteStorage = sDeleteInProgress;
104 // Set the theme before setContentView
105 Theme theme = ThemeManager.getCurrentTheme(this);
106 theme.setBaseTheme(this, true);
108 // Load the dialog's custom layout
109 ViewGroup v = (ViewGroup) LayoutInflater.from(this).inflate(
110 R.layout.unlock_dialog_message, null);
111 mMessage = (TextView) v.findViewById(R.id.unlock_dialog_message);
112 mOldKey = (EditText) v.findViewById(R.id.unlock_old_password);
113 mOldKey.addTextChangedListener(this);
114 mKey = (EditText) v.findViewById(R.id.unlock_password);
115 mKey.addTextChangedListener(this);
116 mRepeatKey = (EditText) v.findViewById(R.id.unlock_repeat);
117 mRepeatKey.addTextChangedListener(this);
118 View oldPasswordLayout = v.findViewById(R.id.unlock_old_password_layout);
119 View repeatLayout = v.findViewById(R.id.unlock_repeat_layout);
120 mValidationMsg = (TextView) v.findViewById(R.id.unlock_validation_msg);
123 int messageResourceId = R.string.secure_storage_unlock_key_prompt_msg;
124 int positiveButtonLabelResourceId = R.string.secure_storage_unlock_button;
125 String title = getString(R.string.secure_storage_unlock_title);
127 positiveButtonLabelResourceId = R.string.secure_storage_create_button;
128 title = getString(R.string.secure_storage_create_title);
129 messageResourceId = R.string.secure_storage_unlock_key_new_msg;
130 } else if (mResetPassword) {
131 positiveButtonLabelResourceId = R.string.secure_storage_reset_button;
132 title = getString(R.string.secure_storage_reset_title);
133 messageResourceId = R.string.secure_storage_unlock_key_reset_msg;
134 TextView passwordLabel = (TextView) v.findViewById(R.id.unlock_password_title);
135 passwordLabel.setText(R.string.secure_storage_unlock_new_key_title);
136 } else if (mDeleteStorage) {
137 positiveButtonLabelResourceId = R.string.secure_storage_delete_button;
138 title = getString(R.string.secure_storage_delete_title);
139 messageResourceId = R.string.secure_storage_unlock_key_delete_msg;
142 // Set the message according to the storage creation status
143 mMessage.setText(messageResourceId);
144 repeatLayout.setVisibility(mNewStorage || mResetPassword ? View.VISIBLE : View.GONE);
145 oldPasswordLayout.setVisibility(mResetPassword ? View.VISIBLE : View.GONE);
147 // Set validation msg
148 mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length,
149 MIN_PASSWORD_LENGTH));
150 mValidationMsg.setVisibility(View.VISIBLE);
153 mDialog = DialogHelper.createTwoButtonsDialog(this,
154 positiveButtonLabelResourceId, R.string.cancel,
155 theme.getResourceId(this,"ic_secure_drawable"), title, v, this);
156 mDialog.setOnDismissListener(new OnDismissListener() {
158 public void onDismiss(DialogInterface dialog) {
163 synchronized (WAIT_SYNC) {
168 mDialog.setOnCancelListener(new OnCancelListener() {
170 public void onCancel(DialogInterface dialog) {
171 sUnlockKeyTemp = null;
176 synchronized (WAIT_SYNC) {
181 mDialog.setCanceledOnTouchOutside(false);
183 // Apply the theme to the custom view of the dialog
188 public void onAttachedToWindow() {
189 super.onAttachedToWindow();
191 DialogHelper.delegateDialogShow(this, mDialog);
192 mUnlock = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
193 mUnlock.setEnabled(false);
197 public void onClick(DialogInterface dialog, int which) {
199 case DialogInterface.BUTTON_POSITIVE:
200 // Create the AES parameter and set to the prompting view
201 if (mResetPassword) {
202 AesCipherParameters params = new AesCipherParameters();
203 params.setPassword(mOldKey.getText().toString().toCharArray());
204 params.setKeyStrength(KeyStrength.BITS_128);
205 sOldUnlockKeyTemp = params;
207 AesCipherParameters params = new AesCipherParameters();
208 params.setPassword(mKey.getText().toString().toCharArray());
209 params.setKeyStrength(KeyStrength.BITS_128);
210 sUnlockKeyTemp = params;
212 // We ended with this dialog
216 case DialogInterface.BUTTON_NEGATIVE:
217 // User had cancelled the dialog
228 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
233 public void onTextChanged(CharSequence s, int start, int before, int count) {
238 public void afterTextChanged(Editable s) {
240 // * Key must be MIN_PASSWORD_LENGTH characters or more
242 String oldkey = mOldKey.getText().toString();
243 String key = mKey.getText().toString();
244 String repeatKey = mRepeatKey.getText().toString();
245 boolean validLength = key.length() >= MIN_PASSWORD_LENGTH &&
246 (!mResetPassword || (mResetPassword && oldkey.length() >= MIN_PASSWORD_LENGTH));
247 boolean validEquals = key.equals(repeatKey);
248 boolean valid = validLength &&
249 (((mNewStorage || mResetPassword) && validEquals) || mDeleteStorage);
250 mUnlock.setEnabled(valid);
253 mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length,
254 MIN_PASSWORD_LENGTH));
255 mValidationMsg.setVisibility(View.VISIBLE);
256 } else if ((mNewStorage || mResetPassword) && !validEquals) {
257 mValidationMsg.setText(R.string.secure_storage_unlock_validation_equals);
258 mValidationMsg.setVisibility(View.VISIBLE);
260 mValidationMsg.setVisibility(View.INVISIBLE);
264 private void applyTheme(Context ctx, ViewGroup root) {
265 // Apply the current theme
266 Theme theme = ThemeManager.getCurrentTheme(ctx);
267 theme.setBackgroundDrawable(ctx, root, "background_drawable");
268 theme.setTextColor(ctx, mMessage, "text_color");
269 theme.setTextColor(ctx, mOldKey, "text_color");
270 theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_old_password_title),
272 theme.setTextColor(ctx, mKey, "text_color");
273 theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_password_title),
275 theme.setTextColor(ctx, mRepeatKey, "text_color");
276 theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_repeat_title),
278 theme.setTextColor(ctx, mValidationMsg, "text_color");
279 mValidationMsg.setCompoundDrawablesWithIntrinsicBounds(
280 theme.getDrawable(ctx, "filesystem_dialog_warning_drawable"), //$NON-NLS-1$
285 SecureStorageKeyPromptDialog() {
287 sResetInProgress = false;
288 sDeleteInProgress = false;
289 sOldUnlockKey = null;
294 public void promptWriteKey(Controller<AesCipherParameters> controller)
295 throws UnknownKeyException {
296 controller.setKey(getOrPromptForKey(false));
297 if (sResetInProgress) {
298 // Not needed any more. Reads are now done with new key
299 sOldUnlockKey = null;
300 sResetInProgress = false;
305 public void promptReadKey(Controller<AesCipherParameters> controller, boolean invalid)
306 throws UnknownKeyException {
307 if (!sResetInProgress && invalid) {
310 controller.setKey(getOrPromptForKey(true));
317 // Discard current keys
318 sResetInProgress = false;
319 sDeleteInProgress = false;
320 sOldUnlockKey = null;
328 // Discard current keys
329 sResetInProgress = true;
330 sDeleteInProgress = false;
331 sOldUnlockKey = null;
339 sDeleteInProgress = true;
340 sResetInProgress = false;
341 sOldUnlockKey = null;
345 * Method that return or prompt the user for the secure storage key
347 * @param read If should return the read or write key
348 * @return AesCipherParameters The AES cipher parameters
350 private static synchronized AesCipherParameters getOrPromptForKey(boolean read)
351 throws UnknownKeyException {
352 // Check if we have a cached key
353 if (read && sResetInProgress && sOldUnlockKey != null) {
354 return sOldUnlockKey;
356 if (sUnlockKey != null) {
360 // Need to prompt the user for the secure storage key, so we open a overlay activity
361 // to show the prompt dialog
362 Handler handler = new Handler(Looper.getMainLooper()) {
364 public void handleMessage(Message inputMessage) {
365 Context ctx = FileManagerApplication.getInstance();
366 Intent intent = new Intent(ctx, SecureStorageKeyPromptActivity.class);
367 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368 ctx.startActivity(intent);
371 handler.sendEmptyMessage(MSG_REQUEST_UNLOCK_DIALOG);
373 // Wait for the response
374 synchronized (WAIT_SYNC) {
377 } catch (InterruptedException ex) {
378 throw new KeyPromptingInterruptedException(ex);
382 // Request for authentication is done. We need to exit from delete status
383 sDeleteInProgress = false;
385 // Check if the user cancelled the dialog
386 if (sUnlockKeyTemp == null) {
387 throw new KeyPromptingCancelledException();
390 // Move temporary params to real params
391 sUnlockKey = sUnlockKeyTemp;
392 sOldUnlockKey = sOldUnlockKeyTemp;
394 AesCipherParameters key = sUnlockKey;
395 if (sResetInProgress && read) {