/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this 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.Views; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Environment; import android.support.v7.app.ActionBarActivity; import android.text.InputType; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.GridView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewAnimator; import com.doplgangr.secrecy.Config; import com.doplgangr.secrecy.CustomApp; import com.doplgangr.secrecy.FileSystem.Vault; import com.doplgangr.secrecy.Jobs.AddFileJob; import com.doplgangr.secrecy.Jobs.InitializeVaultJob; import com.doplgangr.secrecy.Listeners; import com.doplgangr.secrecy.R; import com.doplgangr.secrecy.Util; import com.ipaulpro.afilechooser.FileChooserActivity; import com.nineoldandroids.view.ViewHelper; import org.androidannotations.annotations.Background; import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.FragmentArg; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenuItem; import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.res.DrawableRes; import java.io.File; import java.util.ArrayList; import de.greenrobot.event.EventBus; @EFragment(R.layout.list_file) @OptionsMenu(R.menu.filelist) public class FilesListFragment extends FileViewer { private static final int REQUEST_CODE = 6384; // onActivityResult request code private static final ArrayList<String> INCLUDE_EXTENSIONS_LIST = new ArrayList<String>(); @ViewById(android.R.id.list) ListView listView = null; @ViewById(R.id.gridView) GridView gridView = null; @ViewById(R.id.nothing) View nothing; @ViewById(R.id.progressBar) ProgressBar addFilepBar; @ViewById(R.id.actionBarTitle) TextView mActionBarTitle; @ViewById(R.id.header) View mHeader; @ViewById(R.id.tag) TextView mTag; @OptionsMenuItem(R.id.action_switch_interface) MenuItem switchInterface; @DrawableRes(R.drawable.file_selector) Drawable selector; @FragmentArg(Config.vault_extra) String vault; @FragmentArg(Config.password_extra) String password; private Vault secret; private FilesListAdapter adapter; private int decryptCounter = 0; private boolean isGallery = false; private boolean attached = false; private AbsListView mListView; private CustomActionMode mActionMode; @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); switchInterface.setIcon(isGallery ? R.drawable.ic_list : R.drawable.ic_gallery); } @UiThread void switchView(View parentView, int showView) { if (parentView == null) return; FilesListAdapter.ViewHolder holder = (FilesListAdapter.ViewHolder) parentView.getTag(); ViewAnimator viewAnimator = holder.animator; viewAnimator.setInAnimation(context, R.anim.slide_down); int viewIndex = 0; switch (showView) { case R.id.dataLayout: viewIndex = 0; break; case R.id.DecryptLayout: viewIndex = 1; break; } viewAnimator.setDisplayedChild(viewIndex); holder.page = viewIndex; } @Override public void onAttach(Activity activity) { super.onAttach(activity); attached = true; } @Override public void onDetach() { super.onDetach(); attached = false; } @Background @Override void onCreate() { context = (ActionBarActivity) getActivity(); if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this); CustomApp.jobManager.addJobInBackground(new InitializeVaultJob(vault, password)); } @Background(id = Config.cancellable_task) void addFiles() { secret.iterateAllFiles( new Vault.onFileFoundListener() { @Override public void dothis(com.doplgangr.secrecy.FileSystem.File file) { addToList(file); } }); } public void onEventMainThread(AddFileJob.NewFileEvent event) { // Add new file to the list, sort it to its alphabetical position, and highlight // it with smooth scrolling. if ((adapter != null) && (attached)) { Util.toast(context, CustomApp.context.getString(R.string.Files__add_successful), Toast.LENGTH_SHORT); addToList(event.file); adapter.sort(); int index = adapter.getItemId(event.file); if (index != -1) listView.smoothScrollToPosition(index); } } public void onEventMainThread(Vault vault) { //The vault finishes initializing, is prepared to be populated. secret = vault; if (secret.wrongPass) { Util.alert( context, getString(R.string.Error__open_vault), getString(R.string.Error__open_vault_message), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { finish(); //Done for now -_-' } }, null ); return; } addFiles(); mActionBarTitle.setText(secret.getName()); adapter = new FilesListAdapter(context, isGallery ? R.layout.gallery_item : R.layout.file_item); setupViews(); } public void onEventMainThread(FilesActivity.OnBackPressedEvent onBackPressedEvent) { //Back is pressed. End action mode if it is started. if (onBackPressedEvent.activity == context && mActionMode != null && mActionMode.isActionMode) mActionMode.endActionMode(); else EventBus.getDefault().post(new OnBackPressedUnhandledEvent(onBackPressedEvent.activity)); } @UiThread void setupViews() { mTag.setText(isGallery ? R.string.Page_header__gallery : R.string.Page_header__files); mListView = isGallery ? gridView : listView; if (isGallery) gridView.setAdapter(adapter); else listView.setAdapter(adapter); listView.setVisibility(View.GONE); gridView.setVisibility(View.GONE); mListView.setVisibility(View.VISIBLE); context.supportInvalidateOptionsMenu(); context.getSupportActionBar().setTitle(""); mListView.setEmptyView(nothing); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, final View view, int i, long l) { if (mActionMode != null) { mActionMode.select(i, view); return; } if (isGallery) { Intent intent = new Intent(context, FilePhotoFragment_.class); intent.putExtra(Config.vault_extra, vault); intent.putExtra(Config.password_extra, password); intent.putExtra(Config.gallery_item_extra, i); onPauseDecision.startActivity(); startActivity(intent); } else { com.doplgangr.secrecy.FileSystem.File file = adapter.getItem(i); if (!file.decrypting) { ProgressBar pBar = (ProgressBar) view.findViewById(R.id.progressBar); switchView(view, R.id.DecryptLayout); Listeners.EmptyListener onFinish = new Listeners.EmptyListener() { @Override public void run() { switchView(view, R.id.dataLayout); } }; decrypt(file, pBar, onFinish); } else Util.toast(context, getString(R.string.Error__already_decrypting), Toast.LENGTH_SHORT); } } }); mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) { if (mActionMode == null) mActionMode = new CustomActionMode(); mActionMode.startActionMode(); // Start the CAB using the ActionMode.Callback defined above mActionMode.select(i, view); //switchView(view, R.id.file_actions_layout); //mListView.setOnClickListener(null); return true; } }); mListView.setOnScrollListener(new MaterialScroll().listener); } @UiThread void addToList(com.doplgangr.secrecy.FileSystem.File file) { adapter.add(file); } @Background(id = Config.cancellable_task) @Override void decrypt(com.doplgangr.secrecy.FileSystem.File file, final ProgressBar pBar, Listeners.EmptyListener onFinish) { super.decrypt(file, pBar, onFinish); } @Background(id = Config.cancellable_task) void decrypt_and_save(com.doplgangr.secrecy.FileSystem.File file, final ProgressBar pBar, final Listeners.EmptyListener onFinish) { File tempFile = super.getFile(file, pBar, onFinish); File storedFile = new File(Environment.getExternalStorageDirectory(), file.getName() + "." + file.getType()); if (tempFile == null) { Util.alert(context, CustomApp.context.getString(R.string.Error__decrypting_file), CustomApp.context.getString(R.string.Error__decrypting_file_message), Util.emptyClickListener, null ); return; } tempFile.renameTo(storedFile); } @OptionsItem(R.id.action_switch_interface) void switchInterface() { isGallery = !isGallery; onCreate(); } @OptionsItem(R.id.action_delete_vault) void deleteVault() { final EditText passwordView = new EditText(context); passwordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); passwordView.setHint(R.string.Vault__confirm_password_hint); new AlertDialog.Builder(context) .setTitle(getString(R.string.Vault__confirm_delete)) .setView(passwordView) .setPositiveButton(R.string.OK, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String input = passwordView.getText().toString(); if (password.equals(input)) { secret.delete(); context.finish(); } else { Util.alert(context, CustomApp.context.getString(R.string.Error__delete_password_incorrect), CustomApp.context.getString(R.string.Error__delete_password_incorrect_message), Util.emptyClickListener, null ); } } }) .setNegativeButton(R.string.CANCEL, Util.emptyClickListener) .show(); } @OptionsItem(R.id.action_add_file) void myMethod() { // Use the GET_CONTENT intent from the utility class Intent target = com.ipaulpro.afilechooser.utils.FileUtils.createGetContentIntent(); // Create the chooser Intent Intent intent = Intent.createChooser( target, getString(R.string.Dialog_header__pick_file)); try { startActivityForResult(intent, REQUEST_CODE); onPauseDecision.startActivity(); } catch (ActivityNotFoundException e) { intent = new Intent(context, FileChooserActivity.class); intent.putStringArrayListExtra( FileChooserActivity.EXTRA_FILTER_INCLUDE_EXTENSIONS, INCLUDE_EXTENSIONS_LIST); intent.putExtra(FileChooserActivity.EXTRA_SELECT_FOLDER, false); startActivityForResult(intent, REQUEST_CODE); onPauseDecision.startActivity(); } } public void onActivityResult(int requestCode, int resultCode, Intent data) { onPauseDecision.finishActivity(); if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE && data.getData() != null) { Util.log("intent received=", data.getData().toString(), data.getData().getLastPathSegment()); addFile(secret, data.getData()); super.onActivityResult(requestCode, resultCode, data); } else { Util.toast(context, getString(R.string.Error__no_file_selected), 4000); } } @OptionsItem(R.id.action_decrypt) void decryptCurrentItem() { final ArrayList<FilesListAdapter.ViewNIndex> adapterSelected = new ArrayList<FilesListAdapter.ViewNIndex>(adapter.getSelected()); for (final FilesListAdapter.ViewNIndex object : adapterSelected) { int position = object.index; if (adapter.hasIndex(position)) { com.doplgangr.secrecy.FileSystem.File file = adapter.getItem(position); final View mView = ((FilesListAdapter.ViewHolder) object.view.getTag()).selected ? object.view : null; if (!file.decrypting) { decryptCounter++; switchView(mView, R.id.DecryptLayout); ProgressBar pBar = mView != null ? (ProgressBar) mView.findViewById(R.id.progressBar) : null; Listeners.EmptyListener onFinish = new Listeners.EmptyListener() { @Override public void run() { decryptCounter--; switchView(mView, R.id.dataLayout); if (decryptCounter == 0 && attached) Util.toast(context, getString(R.string.Files__save_to_SD), Toast.LENGTH_SHORT); } }; if (attached) decrypt_and_save(file, pBar, onFinish); } else if (attached) Util.toast(context, getString(R.string.Error__already_decrypting), Toast.LENGTH_SHORT); } } mActionMode.endActionMode(); } @OptionsItem(R.id.action_send) void sendRaw() { ArrayList<DecryptArgHolder> Args = new ArrayList<DecryptArgHolder>(); for (FilesListAdapter.ViewNIndex object : adapter.getSelected()) { int position = object.index; if (adapter.hasIndex(position)) { com.doplgangr.secrecy.FileSystem.File file = adapter.getItem(position); final View mView = ((FilesListAdapter.ViewHolder) object.view.getTag()).selected ? object.view : null; if (!file.decrypting) { switchView(mView, R.id.DecryptLayout); ProgressBar pBar = mView != null ? (ProgressBar) mView.findViewById(R.id.progressBar) : null; Listeners.EmptyListener onFinish = new Listeners.EmptyListener() { @Override public void run() { switchView(mView, R.id.dataLayout); } }; Args.add(new DecryptArgHolder(file, pBar, onFinish)); } else if (attached) Util.toast(context, getString(R.string.Error__already_decrypting), Toast.LENGTH_SHORT); } } if (attached) sendMultiple(Args); mActionMode.endActionMode(); } @OptionsItem(R.id.action_delete) void deleteCurrentItem() { final ArrayList<FilesListAdapter.ViewNIndex> adapterSelected = new ArrayList<FilesListAdapter.ViewNIndex>(adapter.getSelected()); DialogInterface.OnClickListener positive = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { for (FilesListAdapter.ViewNIndex object : adapterSelected) { int position = object.index; if (adapter.hasIndex(object.index)) if (!adapter.getItem(position).decrypting) { adapter.getItem(position).delete(); adapter.remove(position); } else if (attached) Util.toast(context, getString(R.string.Error__already_decrypting_delete), Toast.LENGTH_SHORT); } } }; String FilesToDelete = "\n"; for (FilesListAdapter.ViewNIndex object : adapterSelected) if (adapter.hasIndex(object.index)) FilesToDelete += "- " + adapter.getItem(object.index).getName() + "\n"; DialogInterface.OnClickListener negative = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }; Util.alert(context, getString(R.string.Files__delete), String.format(getString(R.string.Files__delete_message), FilesToDelete), positive, negative ); mActionMode.endActionMode(); } @Override void afterDecrypt(Intent newIntent, Intent altIntent) { if (attached) super.afterDecrypt(newIntent, altIntent); // check if fragment is attached. } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); if (mActionMode != null && mActionMode.isActionMode) inflater.inflate(R.menu.file_action, menu); else inflater.inflate(R.menu.filelist, menu); super.onCreateOptionsMenu(menu, inflater); } class DecryptArgHolder { public com.doplgangr.secrecy.FileSystem.File file; public ProgressBar pBar; public Listeners.EmptyListener onFinish; public DecryptArgHolder(com.doplgangr.secrecy.FileSystem.File file, ProgressBar pBar, Listeners.EmptyListener onFinish) { this.file = file; this.pBar = pBar; this.onFinish = onFinish; } } class CustomActionMode { public boolean isActionMode = false; void startActionMode() { isActionMode = true; context.supportInvalidateOptionsMenu(); } void endActionMode() { adapter.notifyDataSetChanged(); for (int i = 0; i < mListView.getChildCount(); i++) { View child = mListView.getChildAt(i); ((FrameLayout) child.findViewById(R.id.frame)) .setForeground(null); } adapter.clearSelected(); isActionMode = false; new MaterialScroll().listener.onScroll(null, 0, 0, 0); //reset everything context.supportInvalidateOptionsMenu(); } void select(int position, View mView) { FilesListAdapter.ViewHolder viewHolder = (FilesListAdapter.ViewHolder) mView.getTag(); viewHolder.selected = adapter.select(position, mView); mView.setTag(viewHolder); ((FrameLayout) mView.findViewById(R.id.frame)) .setForeground(viewHolder.selected ? selector : null); new MaterialScroll().setTitle( String.format(getString(R.string.Files__number_selected), adapter.getSelected().size())); if (adapter.getSelected().size() == 0) endActionMode(); } } class MaterialScroll { public int getScrollY() { View c = mListView.getChildAt(0); if (c == null) { return 0; } int firstVisiblePosition = mListView.getFirstVisiblePosition(); int top = c.getTop(); int headerHeight = mHeader.getHeight() + mTag.getHeight(); if (firstVisiblePosition >= 1) { headerHeight = mTag.getHeight(); } return -top + firstVisiblePosition * c.getHeight() + headerHeight; } public void setTitle(CharSequence chars) { context.getSupportActionBar().setTitle(chars); } public AbsListView.OnScrollListener listener = new AbsListView.OnScrollListener() { int mHeaderTextHeight = context.getResources().getDimensionPixelSize(R.dimen.header_text_height); int mActionBarHeight = context.getResources().getDimensionPixelSize(R.dimen.action_bar_height); @Override public void onScrollStateChanged(AbsListView absListView, int i) { onScroll(absListView, i, 0, 0); } @Override public void onScroll(AbsListView absListView, int i, int i2, int i3) { int scrollY = getScrollY(); //sticky actionbar if (scrollY >= 0) { if (!(mActionMode != null && mActionMode.isActionMode)) setTitle(getScrollY() > mActionBarHeight ? secret.getName() : ""); ViewHelper.setTranslationY(mHeader, Math.max(-scrollY, -mHeaderTextHeight)); mActionBarTitle.setVisibility(scrollY > mActionBarHeight ? View.GONE : View.VISIBLE); ViewHelper.setTranslationY(mTag, -scrollY); } } }; } public class OnBackPressedUnhandledEvent { public Activity activity; public OnBackPressedUnhandledEvent(Activity activity) { this.activity = activity; } } }