/* Tibisay Movil Copyright (C) 2013 Antonio Araujo (aaraujo@cenditel.gob.ve), Jose Ruiz (jruiz@cenditel.gob.ve), Fundacion Centro Nacional de Desarrollo e Investigacion en Tecnologias Libres - CENDITEL. La Fundación CENDITEL concede permiso para usar, copiar, distribuir y/o modificar este programa, reconociendo el derecho que la humanidad posee al libre acceso al conocimiento, bajo los términos de la licencia de software GPL versión 2.0 de la Free Software Foundation. Este programa se distribuye con la esperanza de que sea util, pero SIN NINGUNA GARANTIA; tampoco las implicitas garantias de MERCANTILIDAD o ADECUACION A UN PROPOSITO PARTICULAR. Para mayor información sobre los términos de la licencia ver el archivo llamado "gpl-2.0.txt" en ingles. */ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed 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 ve.gob.cenditel.tibisaymovil; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.Activity; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; /** * Activity that mimics the Android >= 4.0 Keychain. {@link KeyChainStrategy} automatically starts this activity * when needed. * * @author José M. Prieto (jmprieto@emergya.com) */ public class KeyChainActivity extends Activity implements OnClickListener, OnItemClickListener { private KeyChainView viewHolder; private CustomKeyChain keyChain; private int numChecks; private CertificateAdapter adapt; @Override public void onCreate(Bundle savedInstanceState) { //Estilando la barra de titulo final boolean customTitleSupported = requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); super.onCreate(savedInstanceState); this.keyChain = CustomKeyChain.getInstance(); this.viewHolder = new KeyChainView(); //Eliminate menu. Not used for now. if (this.getIntent()!=null){ boolean quitmenu = this.getIntent().getBooleanExtra("quitmenu", false); if (quitmenu) { this.viewHolder.accept.setVisibility(View.GONE); this.viewHolder.cancel.setVisibility(View.GONE); } }else{ this.viewHolder.accept.setVisibility(View.VISIBLE); this.viewHolder.cancel.setVisibility(View.VISIBLE); } try { this.adapt = new KeyChainActivity.CertificateAdapter(this.keyChain); this.viewHolder.aliases.setAdapter(adapt); } catch (KeystoreException e) { Toast.makeText(this, R.string.error_unknown, Toast.LENGTH_LONG).show(); //throw new RuntimeException(e); } if (this.adapt!=null && this.adapt.aliases.size()==1){ this.numChecks=1; this.viewHolder.aliases.setItemChecked(0, true); }else this.numChecks = 0; this.updateCheckedNumber(); this.updateButtons(); //Estilando Barra de titulo if(customTitleSupported) getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_bar); } @Override protected void onResume(){ super.onResume(); //TODO: this.keyChain.reloadKeyStore(); if (this.adapt!=null) this.adapt.notifyDataSetChanged(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("numChecks", this.numChecks); } @Override public boolean onCreateOptionsMenu(Menu menu) { /*getMenuInflater().inflate(R.menu.keychain_menu, menu);*/ return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.change_password: changePassword(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.cert_chooser_install_button: Intent intent = new Intent(this, PKCS12FilePickerActivity.class); startActivityForResult(intent, ActivityResult.IMPORT_CERTIFICATE); break; case R.id.button_cancel: setResult(RESULT_CANCELED); finish(); break; case R.id.button_accept: if (this.numChecks==1){ acceptSelectedCertificate(); } break; case R.id.button_delete: if (this.numChecks>0) deleteSelectedCertificates(); break; } } private void acceptSelectedCertificate() { Toast.makeText(this, "accept", Toast.LENGTH_LONG).show(); SparseBooleanArray checkedItems = this.viewHolder.aliases.getCheckedItemPositions(); boolean found=false; int firstPosition=-1; for (int i = 0; i < checkedItems.size() && found==false; i++) { Toast.makeText(this, "i="+i, Toast.LENGTH_LONG).show(); if( checkedItems.valueAt(i)== true){ found=true; firstPosition=checkedItems.keyAt(i); } } if (firstPosition!=-1){ KeyChainStrategy.activity.setAlias((String) this.viewHolder.aliases.getItemAtPosition(firstPosition)); setResult(Activity.RESULT_OK); finish(); } } private void deleteSelectedCertificates() { SparseBooleanArray checkedItems = this.viewHolder.aliases.getCheckedItemPositions(); for (int i = 0; i < checkedItems.size(); i++) { if(checkedItems.valueAt(i) == true){ try { int position = checkedItems.keyAt(i); KeyChainStrategy.getInstance().deleteCertificate( (String) this.viewHolder.aliases.getItemAtPosition(position)); } catch (KeystoreException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } //Refresh adapter. NotifyDataSetChange didnt work. try { KeyChainActivity.this.viewHolder.aliases.setAdapter( new CertificateAdapter(KeyChainActivity.this.keyChain)); } catch (KeystoreException e) { e.printStackTrace(); } this.updateCheckedNumber(); this.updateButtons(); //Refresh KeyChain for external views. try { this.keyChain.saveKeystore(); this.keyChain.reloadKeyStore(); } catch (KeystoreException e) { } catch (OutOfSpaceError e) { } } @Override public void onItemClick(AdapterView parent, View view, int item, long id) { this.updateCheckedNumber(); this.updateButtons(); } private void updateButtons() { this.viewHolder.delete.setEnabled(this.numChecks>0); this.viewHolder.accept.setEnabled(this.numChecks==1); } private void updateCheckedNumber() { SparseBooleanArray checkedItems = this.viewHolder.aliases.getCheckedItemPositions(); int count = 0; for (int i = 0; i < checkedItems.size(); i++) { if (checkedItems.valueAt(i)) count++; } this.numChecks = count; } @Override protected void onActivityResult(int requestCode, int resultCode, final Intent data) { if (requestCode == ActivityResult.IMPORT_CERTIFICATE && resultCode == Activity.RESULT_OK) { final PasswordDialog dialog = new PasswordDialog(this, R.string.certificate_password, false); dialog.setAcceptListener(new OnClickListener() { @Override public void onClick(View v) { try { int imported = KeyChainActivity.this.keyChain.importCertificate(data.getData(), dialog.getPassword()); KeyChainActivity.this.keyChain.saveKeystore(); String text = KeyChainActivity.this.getResources().getQuantityString(R.plurals.number_imported_certificates, imported, imported); Toast.makeText(KeyChainActivity.this, text, Toast.LENGTH_LONG).show(); KeyChainActivity.this.viewHolder.aliases.setAdapter( new CertificateAdapter(KeyChainActivity.this.keyChain)); } catch (IncorrectPasswordException e) { Toast toast = Toast.makeText(KeyChainActivity.this, R.string.incorrect_password, Toast.LENGTH_LONG); toast.show(); } catch (Exception e) { Toast toast = Toast.makeText(KeyChainActivity.this, R.string.error_reading_p12, Toast.LENGTH_LONG); toast.show(); } dialog.dismiss(); } }); dialog.show(); } } public void changePassword() { final PasswordConfirmationDialog dialog = new PasswordConfirmationDialog(this, R.string.keystore_new_password); dialog.setAcceptListener(new OnClickListener() { @Override public void onClick(View v) { String newPassword = dialog.getPassword(); dialog.dismiss(); try { KeyChainActivity.this.keyChain.saveKeystore(newPassword); Toast.makeText(KeyChainActivity.this, R.string.password_change_success, Toast.LENGTH_LONG).show(); } catch (OutOfSpaceError e) { Toast.makeText(KeyChainActivity.this, R.string.error_no_space, Toast.LENGTH_LONG).show(); } catch (KeystoreException e) { Toast.makeText(KeyChainActivity.this, R.string.error_unknown, Toast.LENGTH_LONG).show(); //throw new RuntimeException(e); } } }); dialog.show(); } private class CertificateAdapter extends BaseAdapter { private final List aliases; private final List subjects = new ArrayList(); private CustomKeyChain keyChain; private CertificateAdapter(CustomKeyChain keyChain) throws KeystoreException { this.aliases = Collections.list(keyChain.getAliases()); this.subjects.addAll(Collections.nCopies(this.aliases.size(), (String) null)); this.keyChain = keyChain; } @Override public int getCount() { return this.aliases.size(); } @Override public String getItem(int adapterPosition) { return this.aliases.get(adapterPosition); } @Override public long getItemId(int adapterPosition) { return adapterPosition; } @Override public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(KeyChainActivity.this); convertView = inflater.inflate(R.layout.cert_item, parent, false); } TextView aliasTextView = (TextView) convertView.findViewById(R.id.cert_item_alias); TextView subjectTextView = (TextView) convertView.findViewById(R.id.cert_item_subject); CheckBox checkBox = (CheckBox) convertView.findViewById(R.id.cert_item_selected); String alias = this.aliases.get(position); convertView.setTag(alias); aliasTextView.setText(alias); String subject = this.subjects.get(position); if (subject == null) { new CertLoader(position, subjectTextView, this.keyChain).execute(); } else { subjectTextView.setText(subject); } ListView lv = (ListView)parent; checkBox.setChecked(lv.getCheckedItemPositions().get(position)); return convertView; } private class CertLoader extends AsyncTask { private final int adapterPosition; private final TextView subjectView; private CustomKeyChain keyChain; private CertLoader(int adapterPosition, TextView subjectView, CustomKeyChain keyChain) { this.adapterPosition = adapterPosition; this.subjectView = subjectView; this.keyChain = keyChain; } @Override protected String doInBackground(Void... params) { String alias = CertificateAdapter.this.aliases.get(this.adapterPosition); X509Certificate cert; try { cert = this.keyChain.getCertificateChain(alias)[0]; // bouncycastle can handle the emailAddress OID of 1.2.840.113549.1.9.1 return cert.getSubjectDN().getName(); } catch (KeystoreException e) { return ""; } } @Override protected void onPostExecute(String subjectString) { CertificateAdapter.this.subjects.set(this.adapterPosition, subjectString); this.subjectView.setText(subjectString); } } } private class KeyChainView { public ListView aliases; public TextView install; public ImageView accept; public ImageView cancel; public TextView delete; public KeyChainView() { setContentView(R.layout.cert_chooser); this.aliases = (ListView) findViewById(R.id.cert_chooser); this.aliases.setOnItemClickListener(KeyChainActivity.this); this.install = (TextView) findViewById(R.id.cert_chooser_install_button); this.install.setOnClickListener(KeyChainActivity.this); this.accept = (ImageView) findViewById(R.id.button_accept); this.accept.setOnClickListener(KeyChainActivity.this); this.cancel = (ImageView) findViewById(R.id.button_cancel); this.cancel.setOnClickListener(KeyChainActivity.this); this.delete = (TextView) findViewById(R.id.button_delete); this.delete.setOnClickListener(KeyChainActivity.this); } } }