334 lines
12 KiB
Java
334 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.ui;
|
|
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.Messenger;
|
|
import android.support.v4.app.Fragment;
|
|
import android.support.v7.widget.LinearLayoutManager;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.TextView;
|
|
|
|
import org.sufficientlysecure.keychain.R;
|
|
import org.sufficientlysecure.keychain.ui.dialog.AddEditSmartPGPAuthorityDialogFragment;
|
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
|
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
|
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
|
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.security.KeyStore;
|
|
import java.security.KeyStoreException;
|
|
import java.security.cert.Certificate;
|
|
import java.security.cert.CertificateException;
|
|
import java.security.cert.CertificateFactory;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
|
|
public class SettingsSmartPGPAuthorityFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener {
|
|
|
|
private ItemTouchHelper mItemTouchHelper;
|
|
|
|
private ArrayList<String> mAuthorities;
|
|
private AuthorityListAdapter mAdapter;
|
|
|
|
public static SettingsSmartPGPAuthorityFragment newInstance(String[] authorities) {
|
|
return new SettingsSmartPGPAuthorityFragment();
|
|
}
|
|
|
|
@Override
|
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
|
|
savedInstanceState) {
|
|
|
|
return inflater.inflate(R.layout.settings_smartpgp_authority_fragment, null);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
List<String> authorities = new LinkedList();
|
|
|
|
try {
|
|
final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getActivity());
|
|
final Enumeration<String> it = ks.aliases();
|
|
|
|
while (it.hasMoreElements()) {
|
|
authorities.add(it.nextElement());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
}
|
|
|
|
mAuthorities = new ArrayList<>(authorities);
|
|
mAdapter = new AuthorityListAdapter(mAuthorities);
|
|
|
|
RecyclerView recyclerView = view.findViewById(R.id.smartpgp_authority_recycler_view);
|
|
recyclerView.setAdapter(mAdapter);
|
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
|
|
|
ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter);
|
|
mItemTouchHelper = new ItemTouchHelper(callback);
|
|
mItemTouchHelper.attachToRecyclerView(recyclerView);
|
|
|
|
// for clicks
|
|
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this));
|
|
|
|
// can't use item decoration because it doesn't move with drag and drop
|
|
// recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));
|
|
|
|
// We have a menu item to show in action bar.
|
|
setHasOptionsMenu(true);
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
|
inflater.inflate(R.menu.smartpgp_authority_pref_menu, menu);
|
|
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
|
|
case R.id.menu_add_smartpgp_authority:
|
|
startAddAuthorityDialog();
|
|
return true;
|
|
|
|
default:
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
}
|
|
|
|
private void startAddAuthorityDialog() {
|
|
startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action.ADD, null, null, -1);
|
|
}
|
|
|
|
private void startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action action,
|
|
final String old_alias, final Uri uri, final int position) {
|
|
Handler returnHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
Bundle data = message.getData();
|
|
final String new_alias = data.getString(AddEditSmartPGPAuthorityDialogFragment.OUT_ALIAS);
|
|
final int position = data.getInt(AddEditSmartPGPAuthorityDialogFragment.OUT_POSITION);
|
|
final String uri = data.getString(AddEditSmartPGPAuthorityDialogFragment.OUT_URI);
|
|
|
|
final AddEditSmartPGPAuthorityDialogFragment.Action action =
|
|
(AddEditSmartPGPAuthorityDialogFragment.Action)
|
|
data.getSerializable(AddEditSmartPGPAuthorityDialogFragment.OUT_ACTION);
|
|
|
|
switch(action) {
|
|
case ADD:
|
|
if (editAuthority(old_alias, new_alias, position, uri)) {
|
|
Notify.create(getActivity(), "Authority " + new_alias + " added",
|
|
Notify.LENGTH_SHORT, Notify.Style.OK).show();
|
|
}
|
|
break;
|
|
|
|
case EDIT:
|
|
if (editAuthority(old_alias, new_alias, position, uri)){
|
|
Notify.create(getActivity(), "Authority " + old_alias + " modified",
|
|
Notify.LENGTH_SHORT, Notify.Style.OK).show();
|
|
}
|
|
break;
|
|
|
|
case DELETE:
|
|
if (deleteAuthority(position)) {
|
|
Notify.create(getActivity(), "Authority " + old_alias + " deleted",
|
|
Notify.LENGTH_SHORT, Notify.Style.OK).show();
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create a new Messenger for the communication back
|
|
Messenger messenger = new Messenger(returnHandler);
|
|
AddEditSmartPGPAuthorityDialogFragment dialogFragment = AddEditSmartPGPAuthorityDialogFragment
|
|
.newInstance(messenger, action, old_alias, uri, position);
|
|
dialogFragment.show(getFragmentManager(), "addSmartPGPAuthorityDialog");
|
|
}
|
|
|
|
|
|
private boolean editAuthority(final String old_alias, final String new_alias, final int position, final String uri) {
|
|
try {
|
|
final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getContext());
|
|
|
|
if (ks == null) {
|
|
throw new KeyStoreException("no keystore found");
|
|
}
|
|
|
|
Certificate old_cert = null;
|
|
if (old_alias != null) {
|
|
old_cert = ks.getCertificate(old_alias);
|
|
ks.deleteEntry(old_alias);
|
|
mAuthorities.remove(old_alias);
|
|
mAdapter.notifyItemRemoved(position);
|
|
}
|
|
|
|
Certificate new_cert = null;
|
|
if (uri == null) {
|
|
new_cert = old_cert;
|
|
} else {
|
|
final InputStream fis = getContext().getContentResolver().openInputStream(Uri.parse(uri));
|
|
|
|
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
new_cert = cf.generateCertificate(fis);
|
|
if (!(new_cert instanceof X509Certificate)) {
|
|
Notify.create(getActivity(), "Invalid certificate", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
|
return false;
|
|
}
|
|
|
|
fis.close();
|
|
}
|
|
|
|
if (new_alias == null || new_cert == null) {
|
|
Notify.create(getActivity(), "Missing alias or certificate", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
|
return false;
|
|
}
|
|
|
|
final X509Certificate x509cert = (X509Certificate)new_cert;
|
|
|
|
x509cert.checkValidity();
|
|
|
|
ks.setCertificateEntry(new_alias, x509cert);
|
|
|
|
SettingsSmartPGPAuthoritiesActivity.writeKeystore(getContext(), ks);
|
|
|
|
mAuthorities.add(new_alias);
|
|
mAdapter.notifyItemInserted(mAuthorities.size() - 1);
|
|
|
|
return true;
|
|
|
|
} catch (IOException e) {
|
|
Notify.create(getActivity(), "failed to open certificate (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
|
} catch (CertificateException e) {
|
|
Notify.create(getActivity(), "invalid certificate (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
|
} catch (KeyStoreException e) {
|
|
Notify.create(getActivity(), "invalid keystore (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private boolean deleteAuthority(final int position) {
|
|
try {
|
|
final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getContext());
|
|
|
|
if (ks == null) {
|
|
return false;
|
|
}
|
|
|
|
ks.deleteEntry(mAuthorities.get(position));
|
|
|
|
SettingsSmartPGPAuthoritiesActivity.writeKeystore(getContext(), ks);
|
|
|
|
mAuthorities.remove(mAuthorities.get(position));
|
|
mAdapter.notifyItemRemoved(position);
|
|
|
|
return true;
|
|
} catch (Exception e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onItemClick(View view, int position) {
|
|
startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action.EDIT,
|
|
mAuthorities.get(position), null, position);
|
|
}
|
|
|
|
|
|
public class AuthorityListAdapter extends RecyclerView.Adapter<AuthorityListAdapter.ViewHolder>
|
|
implements ItemTouchHelperAdapter {
|
|
|
|
private final List<String> mAuthorities;
|
|
|
|
public AuthorityListAdapter(List<String> authorities) {
|
|
mAuthorities = authorities;
|
|
}
|
|
|
|
@Override
|
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
View view = LayoutInflater.from(parent.getContext())
|
|
.inflate(R.layout.settings_smartpgp_authority_item, parent, false);
|
|
return new ViewHolder(view);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
|
holder.authorityName.setText(mAuthorities.get(position));
|
|
}
|
|
|
|
@Override
|
|
public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
|
|
int fromPosition, int toPosition) {
|
|
Collections.swap(mAuthorities, fromPosition, toPosition);
|
|
notifyItemMoved(fromPosition, toPosition);
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return mAuthorities.size();
|
|
}
|
|
|
|
|
|
public class ViewHolder extends RecyclerView.ViewHolder implements
|
|
ItemTouchHelperViewHolder {
|
|
|
|
public final ViewGroup outerLayout;
|
|
public final TextView authorityName;
|
|
|
|
public ViewHolder(View itemView) {
|
|
super(itemView);
|
|
outerLayout = itemView.findViewById(R.id.outer_layout);
|
|
authorityName = itemView.findViewById(R.id.smartpgp_authority_tv);
|
|
itemView.setClickable(true);
|
|
}
|
|
|
|
@Override
|
|
public void onItemSelected() {
|
|
}
|
|
|
|
@Override
|
|
public void onItemClear() {
|
|
}
|
|
}
|
|
}
|
|
}
|