/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with context work for additional information
* regarding copyright ownership. The ASF licenses context file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use context file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.doplgangr.secrecy.adapters;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.ActionBarActivity;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View.OnLongClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.TextView;
import android.widget.ViewAnimator;
import com.doplgangr.secrecy.CustomApp;
import com.doplgangr.secrecy.R;
import com.doplgangr.secrecy.utils.Util;
import com.doplgangr.secrecy.activities.FilesActivity;
import com.doplgangr.secrecy.events.RestoreDoneEvent;
import com.doplgangr.secrecy.events.RestoringFileEvent;
import com.doplgangr.secrecy.filesystem.Storage;
import com.doplgangr.secrecy.filesystem.encryption.Vault;
import com.doplgangr.secrecy.filesystem.encryption.VaultHolder;
import com.doplgangr.secrecy.jobs.RestoreJob;
import com.doplgangr.secrecy.fragments.SettingsFragment;
import com.ipaulpro.afilechooser.FileChooserActivity;
import com.ipaulpro.afilechooser.utils.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import de.greenrobot.event.EventBus;
public class VaultsListFragment extends Fragment {
//Vault restore module
private static final int REQUESTCODE = 1203; //Arbitrary
private LinearLayout mLinearView;
private ScrollView mScrollView;
private View nothing;
private ActionBarActivity context;
private VaultsAdapter adapter;
private OnVaultSelectedListener mOnVaultSelected;
private OnFragmentFinishListener mFinishListener;
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mBuilder;
private static InputMethodManager imm = null;
private static View kbdView = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
context = (ActionBarActivity) getActivity();
if (context == null) {
return;
}
VaultHolder.getInstance().clear();
if (context.getSupportActionBar() != null) {
context.getSupportActionBar().setTitle(R.string.App__name);
}
if (!Util.canWrite(Storage.getRoot())) {
Util.alert(CustomApp.context,
CustomApp.context.getString(R.string.Error__root_IOException),
CustomApp.context.getString(R.string.Error__root_IOException_message),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
mFinishListener.onNew(null, new SettingsFragment());
}
},
null
);
return;
}
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.activity_list_vault, container, false);
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.Page_header__vaults);
mLinearView = (LinearLayout) view.findViewById(R.id.list);
mScrollView = (ScrollView) view.findViewById(R.id.scrollView);
nothing = view.findViewById(R.id.nothing);
loadVaultList();
showTutorial();
return view;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.action_add_vault:
add();
return true;
case R.id.action_restore:
restore();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.list_vault, menu);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mOnVaultSelected = (OnVaultSelectedListener) activity;
mFinishListener = (OnFragmentFinishListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement Listener");
}
}
@Override
public void onDestroy(){
super.onDestroy();
EventBus.getDefault().unregister(this);
}
public VaultsAdapter getAdapter() {
return adapter;
}
private void refresh(){
VaultHolder.getInstance().clear();
loadVaultList();
}
private void loadVaultList(){
if (mLinearView != null) {
mLinearView.removeAllViews();
}
adapter = new VaultsAdapter(context, null);
ArrayList<File> files = Storage.getDirectories(Storage.getRoot());
for (File file : files) {
adapter.add(file.getName());
}
adapter.sort();
for (int i = 0; i < files.size(); i++){
final View mView = adapter.getView(i, mLinearView); //inject vaults into list
mLinearView.addView(mView, i);
setClickListener(mView, i);
}
if (adapter.getCount() == 0) {
nothing.setVisibility(View.VISIBLE);
mLinearView.setVisibility(View.GONE);
} else {
nothing.setVisibility(View.GONE);
mLinearView.setVisibility(View.VISIBLE);
}
}
public void onEventMainThread(FilesActivity.shouldRefresh ignored) {
refresh();
}
public void setClickListener(final View mView, final int i) {
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
open(adapter.getItem(i), mView, i);
}
});
mView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
switchView(mView, R.id.vault_rename_layout);
final EditText nameField = (EditText) mView.findViewById(R.id.rename_name);
nameField.setText(adapter.getItem(i));
// Places current vault name in the field for editing.
final InputListener inputListener = new InputListener(imm, kbdView) {
// Listener for changing the name of the vault.
@Override
void launchAction() {
final String newName = nameField.getText().toString();
switchView(mView, R.id.vault_decrypt_layout);
final EditText passwordField = (EditText)
mView.findViewById(R.id.open_password);
final PasswordListener passwordListener = new PasswordListener(imm, kbdView,
adapter.getItem(i), mOnVaultSelected, passwordField) {
// This needs to be nested so the new name can be accessed. Takes
// password from the displayed password field and sends it to the actual
// verification logic.
@Override
void launchAction() {
String password = passwordField.getText().toString();
rename(i, newName, password);
switchView(mView, R.id.vault_decrypt_layout);
}
};
passwordField.setOnEditorActionListener(passwordListener);
mView.findViewById(R.id.open_ok).setOnClickListener(passwordListener);
}
};
mView.findViewById(R.id.rename_ok).setOnClickListener(inputListener);
nameField.setOnEditorActionListener(inputListener);
// These two apply to the listener for the name, not the password.
View.OnClickListener cancelListener = new View.OnClickListener() {
@Override
public void onClick(View ignored) {
inputListener.hideKeyboard();
switchView(mView, R.id.vault_name_layout);
}
};
mView.findViewById(R.id.open_cancel).setOnClickListener(cancelListener);
mView.findViewById(R.id.rename_cancel).setOnClickListener(cancelListener);
return true;
}
});
}
void add() {
final View dialogView = View.inflate(context, R.layout.new_credentials, null);
final EditText password = new EditText(context);
password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
new AlertDialog.Builder(context)
.setTitle(getString(R.string.Vault__new))
.setView(dialogView)
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String name = ((EditText) dialogView.findViewById(R.id.newName)).getText().toString();
String password = ((EditText) dialogView.findViewById(R.id.stealth_keycode)).getText().toString();
String Confirmpassword = ((EditText) dialogView.findViewById(R.id.confirmPassword)).getText().toString();
File directory = new File(Storage.getRoot().getAbsolutePath() + "/" + name);
if (!password.equals(Confirmpassword) || "".equals(password))
passwordWrong();
else if (directory.mkdirs()) {
// Create vault to initialize the vault header
ProgressDialog progress = new ProgressDialog(context);
progress.setIndeterminate(true);
progress.setMessage(getString(R.string.Vault__initializing));
progress.setCancelable(false);
progress.show();
createVaultInBackground(name, password, directory, dialog, progress);
} else
failedtocreate();
}
}).setNegativeButton(getString(R.string.CANCEL), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
}).show();
}
void restore() {
ArrayList<String> INCLUDE_EXTENSIONS_LIST = new ArrayList<String>();
INCLUDE_EXTENSIONS_LIST.add(".zip");
Intent intent = new Intent(context, FileChooserActivity.class);
intent.putStringArrayListExtra(
FileChooserActivity.EXTRA_FILTER_INCLUDE_EXTENSIONS,
INCLUDE_EXTENSIONS_LIST);
intent.putExtra(FileChooserActivity.PATH, Storage.getRoot().getAbsolutePath());
startActivityForResult(intent, REQUESTCODE);
}
void createVaultInBackground(final String name, final String password,
final File directory, final DialogInterface dialog,
final ProgressDialog progressDialog) {
new AsyncTask<Void, Void, Void>(){
@Override
protected Void doInBackground(Void... voids) {
VaultHolder.getInstance().createAndRetrieveVault(name, password);
try {
File file = new File(directory + "/.nomedia");
file.delete();
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
refresh();
dialog.dismiss();
progressDialog.dismiss();
}
}.execute();
}
void passwordWrong() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(context)
.setTitle(getString(R.string.Error__wrong_password_confirmation))
.setMessage(getString(R.string.Error__wrong_password_confirmation_message))
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).show();
}
});
}
void failedtocreate() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(context)
.setTitle(getString(R.string.Error__cannot_create_vault))
.setMessage(getString(R.string.Error__cannot_create_vault_message))
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).show();
}
});
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
switch (requestCode) {
case REQUESTCODE:
// If the file selection was successful
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
// Get the URI of the selected file
Util.alert(
context,
null,
CustomApp.context.getString(R.string.Restore__overwrite_alert),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final Uri uri = data.getData();
final String path = FileUtils.getPath(context, uri);
mNotifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(context);
mBuilder.setContentTitle(CustomApp.context.getString(R.string.Restore__title))
.setContentText(CustomApp.context.getString(R.string.Restore__in_progress))
.setSmallIcon(R.drawable.ic_stat_alert)
.setOngoing(true);
mBuilder.setProgress(0, 0, true);
mNotifyManager.notify(REQUESTCODE, mBuilder.build());
CustomApp.jobManager.addJobInBackground(new RestoreJob(context, new File(path)));
}
},
Util.emptyClickListener
);
}
}
break;
}
}
});
}
public void onEventMainThread(RestoreDoneEvent event) {
mBuilder.setProgress(0, 0, false)
.setStyle(new NotificationCompat.BigTextStyle().bigText( //For long long text
String.format(CustomApp.context.getString(R.string.Restore__finish), event.backupFile)))
.setOngoing(false);
mNotifyManager.notify(REQUESTCODE, mBuilder.build());
}
public void onEventMainThread(RestoringFileEvent event) {
mBuilder.setContentText(event.restoredFile.getAbsolutePath());
mNotifyManager.notify(REQUESTCODE, mBuilder.build());
}
public void open(final String vault, final View mView, final int i) {
// vault name
// View of lisitem
// position of listitem in list
switchView(mView, R.id.vault_decrypt_layout);
EditText passwordField = (EditText)
mView.findViewById(R.id.open_password);
final PasswordListener passwordListener = new PasswordListener(
imm, kbdView, vault, mOnVaultSelected, passwordField);
passwordField.setOnEditorActionListener(passwordListener);
mView.findViewById(R.id.open_ok).setOnClickListener(passwordListener);
mView.findViewById(R.id.open_cancel)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switchView(mView, R.id.vault_name_layout);
passwordListener.hideKeyboard();
}
});
}
void rename(final int position, final String newName, final String password) {
Vault newVault = VaultHolder.getInstance().createAndRetrieveVault(
adapter.getItem(position), password)
.rename(newName);
if (newVault == null)
Util.alert(context,
getString(R.string.Error__rename_password_incorrect),
getString(R.string.Error__rename_password_incorrect_message),
Util.emptyClickListener,
null
);
refresh();
}
void switchView(final View parentView, final int showView) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
EditText passwordView = (EditText) parentView.findViewById(R.id.open_password);
final View renameView = parentView.findViewById(R.id.rename_name);
ViewAnimator viewAnimator = (ViewAnimator) parentView.findViewById(R.id.viewAnimator);
int viewIndex = 0;
switch (showView) {
case R.id.vault_name_layout:
viewIndex = 0;
break;
case R.id.vault_decrypt_layout:
viewIndex = 1;
if (passwordView != null) {
passwordView.requestFocus();
passwordView.setText(""); //Reset password field everytime
}
// Only one vault selected at that time
passwordView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
ViewAnimator viewAnimator = (ViewAnimator) parentView.findViewById(R.id.viewAnimator);
viewAnimator.setDisplayedChild(0);
}
}
});
kbdView = passwordView;
imm.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT);
break;
case R.id.vault_delete_layout:
viewIndex = 2;
break;
case R.id.vault_rename_layout:
viewIndex = 3;
if (renameView != null)
renameView.requestFocus();
// Only one vault selected at that time
renameView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
ViewAnimator viewAnimator = (ViewAnimator) parentView.findViewById(R.id.viewAnimator);
viewAnimator.setDisplayedChild(0);
}
}
});
kbdView = renameView;
imm.showSoftInput(renameView, InputMethodManager.SHOW_IMPLICIT);
break;
}
viewAnimator.setDisplayedChild(viewIndex);
}
});
}
void finish() {
mFinishListener.onFinish(this);
}
void showTutorial() {
if ((adapter.getCount() > 0
&& PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("showVaultLongClickTutorial", true))) {
final View mView =
context.getLayoutInflater().inflate(R.layout.vault_item_tutorial, mLinearView, false);
TextView mInstructions = (TextView) mView.findViewById(R.id.Tutorial__instruction);
if (mInstructions != null)
mInstructions.setText(R.string.Tutorial__long_click_to_rename);
mLinearView.addView(mView, 0);
mView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
mLinearView.removeView(mView);
SharedPreferences.Editor editor
= PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean("showVaultLongClickTutorial", false);
editor.apply();
return true;
}
});
}
}
public interface OnVaultSelectedListener {
public void onVaultSelected(String vault, String password);
}
public interface OnFragmentFinishListener {
public void onFinish(Fragment fragment);
public void onNew(Bundle bundle, Fragment fragment);
}
public interface onPanic {
void onPanic();
}
/**
* Does an action when the "done" key is pressed or a View is
* clicked.
*/
private abstract class InputListener
implements TextView.OnEditorActionListener, View.OnClickListener {
private final InputMethodManager imm;
private final View kbdView;
InputListener(final InputMethodManager imm, final View kbdView) {
this.imm = imm;
this.kbdView = kbdView;
}
@Override
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
launchAction();
hideKeyboard();
return true;
}
return false;
}
@Override
public void onClick(View v) {
launchAction();
hideKeyboard();
}
public void hideKeyboard() {
imm.hideSoftInputFromWindow(kbdView.getWindowToken(),
InputMethodManager.HIDE_IMPLICIT_ONLY);
}
abstract void launchAction();
}
/**
* Sends a password to try to unlock a vault.
*/
private class PasswordListener extends InputListener {
private final String vault;
private final OnVaultSelectedListener vaultListener;
private final EditText passwordField;
private PasswordListener(
final InputMethodManager imm, final View kbdView,
final String vault,
final OnVaultSelectedListener vaultListener,
final EditText passwordField) {
super(imm, kbdView);
this.vault = vault;
this.vaultListener = vaultListener;
this.passwordField = passwordField;
}
@Override
void launchAction() {
sendPassword();
}
private void sendPassword() {
String password = passwordField.getText().toString();
vaultListener.onVaultSelected(vault, password);
}
}
}