OSDN Git Service

73bb3ad4b9ca7554981511379a21e7af45937fbe
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / console / secure / SecureStorageKeyPromptDialog.java
1 /*
2  * Copyright (C) 2014 The CyanogenMod Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.cyanogenmod.filemanager.console.secure;
18
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;
39
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;
45
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;
52
53 /**
54  * A class that remembers all the secure storage
55  */
56 public class SecureStorageKeyPromptDialog
57     implements de.schlichtherle.truezip.key.PromptingKeyProvider.View<AesCipherParameters> {
58
59     private static final int MIN_PASSWORD_LENGTH = 8;
60
61     private static final int MSG_REQUEST_UNLOCK_DIALOG = 1;
62
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();
70
71     /**
72      * An activity that simulates a dialog over the activity that requested the key prompt.
73      */
74     public static class SecureStorageKeyPromptActivity extends Activity
75             implements OnClickListener, TextWatcher {
76
77         private AlertDialog mDialog;
78
79         private TextView mMessage;
80         private EditText mOldKey;
81         private EditText mKey;
82         private EditText mRepeatKey;
83         private TextView mValidationMsg;
84         private Button mUnlock;
85
86         private boolean mNewStorage;
87         private boolean mResetPassword;
88         private boolean mDeleteStorage;
89
90         AesCipherParameters mOldKeyParams;
91         AesCipherParameters mKeyParams;
92
93         @Override
94         @SuppressWarnings("deprecation")
95         protected void onCreate(Bundle savedInstanceState) {
96             super.onCreate(savedInstanceState);
97
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;
103
104             // Set the theme before setContentView
105             Theme theme = ThemeManager.getCurrentTheme(this);
106             theme.setBaseTheme(this, true);
107
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);
121
122             // Load resources
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);
126             if (mNewStorage) {
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;
140             }
141
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);
146
147             // Set validation msg
148             mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length,
149                     MIN_PASSWORD_LENGTH));
150             mValidationMsg.setVisibility(View.VISIBLE);
151
152             // Create the dialog
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() {
157                 @Override
158                 public void onDismiss(DialogInterface dialog) {
159                     mDialog.dismiss();
160                     finish();
161
162                     // Unlock the wait
163                     synchronized (WAIT_SYNC) {
164                         WAIT_SYNC.notify();
165                     }
166                 }
167             });
168             mDialog.setOnCancelListener(new OnCancelListener() {
169                 @Override
170                 public void onCancel(DialogInterface dialog) {
171                     sUnlockKeyTemp = null;
172                     mDialog.cancel();
173                     finish();
174
175                     // Unlock the wait
176                     synchronized (WAIT_SYNC) {
177                         WAIT_SYNC.notify();
178                     }
179                 }
180             });
181             mDialog.setCanceledOnTouchOutside(false);
182
183             // Apply the theme to the custom view of the dialog
184             applyTheme(this, v);
185         }
186
187         @Override
188         public void onAttachedToWindow() {
189             super.onAttachedToWindow();
190
191             DialogHelper.delegateDialogShow(this, mDialog);
192             mUnlock = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
193             mUnlock.setEnabled(false);
194         }
195
196         @Override
197         public void onClick(DialogInterface dialog, int which) {
198             switch (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;
206                     }
207                     AesCipherParameters params = new AesCipherParameters();
208                     params.setPassword(mKey.getText().toString().toCharArray());
209                     params.setKeyStrength(KeyStrength.BITS_128);
210                     sUnlockKeyTemp = params;
211
212                     // We ended with this dialog
213                     dialog.dismiss();
214                     break;
215
216                 case DialogInterface.BUTTON_NEGATIVE:
217                     // User had cancelled the dialog
218                     dialog.cancel();
219
220                     break;
221
222                 default:
223                     break;
224             }
225         }
226
227         @Override
228         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
229             // Ignore
230         }
231
232         @Override
233         public void onTextChanged(CharSequence s, int start, int before, int count) {
234             // Ignore
235         }
236
237         @Override
238         public void afterTextChanged(Editable s) {
239             // Validations:
240             //  * Key must be MIN_PASSWORD_LENGTH characters or more
241             //  * Repeat == Key
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);
251
252             if (!validLength) {
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);
259             } else {
260                 mValidationMsg.setVisibility(View.INVISIBLE);
261             }
262         }
263
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),
271                     "text_color");
272             theme.setTextColor(ctx, mKey, "text_color");
273             theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_password_title),
274                     "text_color");
275             theme.setTextColor(ctx, mRepeatKey, "text_color");
276             theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_repeat_title),
277                     "text_color");
278             theme.setTextColor(ctx, mValidationMsg, "text_color");
279             mValidationMsg.setCompoundDrawablesWithIntrinsicBounds(
280                     theme.getDrawable(ctx, "filesystem_dialog_warning_drawable"), //$NON-NLS-1$
281                     null, null, null);
282         }
283     }
284
285     SecureStorageKeyPromptDialog() {
286         super();
287         sResetInProgress = false;
288         sDeleteInProgress = false;
289         sOldUnlockKey = null;
290         sUnlockKey = null;
291     }
292
293     @Override
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;
301         }
302     }
303
304     @Override
305     public void promptReadKey(Controller<AesCipherParameters> controller, boolean invalid)
306             throws UnknownKeyException {
307         if (!sResetInProgress && invalid) {
308             sUnlockKey = null;
309         }
310         controller.setKey(getOrPromptForKey(true));
311     }
312
313     /**
314      * {@hide}
315      */
316     void umount() {
317         // Discard current keys
318         sResetInProgress = false;
319         sDeleteInProgress = false;
320         sOldUnlockKey = null;
321         sUnlockKey = null;
322     }
323
324     /**
325      * {@hide}
326      */
327     void reset() {
328         // Discard current keys
329         sResetInProgress = true;
330         sDeleteInProgress = false;
331         sOldUnlockKey = null;
332         sUnlockKey = null;
333     }
334
335     /**
336      * {@hide}
337      */
338     void delete() {
339         sDeleteInProgress = true;
340         sResetInProgress = false;
341         sOldUnlockKey = null;
342     }
343
344     /**
345      * Method that return or prompt the user for the secure storage key
346      *
347      * @param read If should return the read or write key
348      * @return AesCipherParameters The AES cipher parameters
349      */
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;
355         }
356         if (sUnlockKey != null) {
357             return sUnlockKey;
358         }
359
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()) {
363             @Override
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);
369             }
370         };
371         handler.sendEmptyMessage(MSG_REQUEST_UNLOCK_DIALOG);
372
373         // Wait for the response
374         synchronized (WAIT_SYNC) {
375             try {
376                 WAIT_SYNC.wait();
377             } catch (InterruptedException ex) {
378                 throw new KeyPromptingInterruptedException(ex);
379             }
380         }
381
382         // Request for authentication is done. We need to exit from delete status
383         sDeleteInProgress = false;
384
385         // Check if the user cancelled the dialog
386         if (sUnlockKeyTemp == null) {
387             throw new KeyPromptingCancelledException();
388         }
389
390         // Move temporary params to real params
391         sUnlockKey = sUnlockKeyTemp;
392         sOldUnlockKey = sOldUnlockKeyTemp;
393
394         AesCipherParameters key = sUnlockKey;
395         if (sResetInProgress && read) {
396             key = sOldUnlockKey;
397         }
398         return key;
399     }
400 }