OSDN Git Service

Update to r505
[android-x86/packages-apps-ConnectBot.git] / src / org / connectbot / PubkeyListActivity.java
1 /*
2  * ConnectBot: simple, powerful, open-source SSH client for Android
3  * Copyright 2007 Kenny Root, Jeffrey Sharkey
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.connectbot;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.URI;
26 import java.security.KeyPair;
27 import java.security.PrivateKey;
28 import java.security.PublicKey;
29 import java.util.Collections;
30 import java.util.EventListener;
31 import java.util.LinkedList;
32 import java.util.List;
33
34 import org.connectbot.bean.PubkeyBean;
35 import org.connectbot.service.TerminalManager;
36 import org.connectbot.util.PubkeyDatabase;
37 import org.connectbot.util.PubkeyUtils;
38 import org.openintents.intents.FileManagerIntents;
39
40 import android.app.AlertDialog;
41 import android.app.ListActivity;
42 import android.content.ActivityNotFoundException;
43 import android.content.ComponentName;
44 import android.content.Context;
45 import android.content.DialogInterface;
46 import android.content.Intent;
47 import android.content.ServiceConnection;
48 import android.content.DialogInterface.OnClickListener;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.Environment;
52 import android.os.Handler;
53 import android.os.IBinder;
54 import android.os.Message;
55 import android.text.ClipboardManager;
56 import android.util.Log;
57 import android.view.ContextMenu;
58 import android.view.LayoutInflater;
59 import android.view.Menu;
60 import android.view.MenuItem;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.MenuItem.OnMenuItemClickListener;
64 import android.widget.AdapterView;
65 import android.widget.ArrayAdapter;
66 import android.widget.EditText;
67 import android.widget.ImageView;
68 import android.widget.TableRow;
69 import android.widget.TextView;
70 import android.widget.Toast;
71 import android.widget.AdapterView.OnItemClickListener;
72
73 import com.trilead.ssh2.crypto.Base64;
74 import com.trilead.ssh2.crypto.PEMDecoder;
75 import com.trilead.ssh2.crypto.PEMStructure;
76
77 /**
78  * List public keys in database by nickname and describe their properties. Allow users to import,
79  * generate, rename, and delete key pairs.
80  *
81  * @author Kenny Root
82  */
83 public class PubkeyListActivity extends ListActivity implements EventListener {
84         public final static String TAG = "ConnectBot.PubkeyListActivity";
85
86         private static final int MAX_KEYFILE_SIZE = 8192;
87         private static final int REQUEST_CODE_PICK_FILE = 1;
88
89         // Constants for AndExplorer's file picking intent
90         private static final String ANDEXPLORER_TITLE = "explorer_title";
91         private static final String MIME_TYPE_ANDEXPLORER_FILE = "vnd.android.cursor.dir/lysesoft.andexplorer.file";
92
93         protected PubkeyDatabase pubkeydb;
94         private List<PubkeyBean> pubkeys;
95
96         protected ClipboardManager clipboard;
97
98         protected LayoutInflater inflater = null;
99
100         protected TerminalManager bound = null;
101
102         private MenuItem onstartToggle = null;
103         private MenuItem confirmUse = null;
104
105         private ServiceConnection connection = new ServiceConnection() {
106                 public void onServiceConnected(ComponentName className, IBinder service) {
107                         bound = ((TerminalManager.TerminalBinder) service).getService();
108
109                         // update our listview binder to find the service
110                         updateList();
111                 }
112
113                 public void onServiceDisconnected(ComponentName className) {
114                         bound = null;
115                         updateList();
116                 }
117         };
118
119         @Override
120         public void onStart() {
121                 super.onStart();
122
123                 bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE);
124
125                 if(pubkeydb == null)
126                         pubkeydb = new PubkeyDatabase(this);
127         }
128
129         @Override
130         public void onStop() {
131                 super.onStop();
132
133                 unbindService(connection);
134
135                 if(pubkeydb != null) {
136                         pubkeydb.close();
137                         pubkeydb = null;
138                 }
139         }
140
141         @Override
142         public void onCreate(Bundle icicle) {
143                 super.onCreate(icicle);
144                 setContentView(R.layout.act_pubkeylist);
145
146                 this.setTitle(String.format("%s: %s",
147                                 getResources().getText(R.string.app_name),
148                                 getResources().getText(R.string.title_pubkey_list)));
149
150                 // connect with hosts database and populate list
151                 pubkeydb = new PubkeyDatabase(this);
152
153                 updateList();
154
155                 registerForContextMenu(getListView());
156
157                 getListView().setOnItemClickListener(new OnItemClickListener() {
158                         public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
159                                 PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(position);
160                                 boolean loaded = bound.isKeyLoaded(pubkey.getNickname());
161
162                                 // handle toggling key in-memory on/off
163                                 if(loaded) {
164                                         bound.removeKey(pubkey.getNickname());
165                                         updateHandler.sendEmptyMessage(-1);
166                                 } else {
167                                         handleAddKey(pubkey);
168                                 }
169
170                         }
171                 });
172
173                 clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
174
175                 inflater = LayoutInflater.from(this);
176         }
177
178         /**
179          * Read given file into memory as <code>byte[]</code>.
180          */
181         protected static byte[] readRaw(File file) throws Exception {
182                 InputStream is = new FileInputStream(file);
183                 ByteArrayOutputStream os = new ByteArrayOutputStream();
184
185                 int bytesRead;
186                 byte[] buffer = new byte[1024];
187                 while ((bytesRead = is.read(buffer)) != -1) {
188                         os.write(buffer, 0, bytesRead);
189                 }
190
191                 os.flush();
192                 os.close();
193                 is.close();
194
195                 return os.toByteArray();
196
197         }
198
199         @Override
200         public boolean onCreateOptionsMenu(Menu menu) {
201                 super.onCreateOptionsMenu(menu);
202
203                 MenuItem generatekey = menu.add(R.string.pubkey_generate);
204                 generatekey.setIcon(android.R.drawable.ic_menu_manage);
205                 generatekey.setIntent(new Intent(PubkeyListActivity.this, GeneratePubkeyActivity.class));
206
207                 MenuItem importkey = menu.add(R.string.pubkey_import);
208                 importkey.setIcon(android.R.drawable.ic_menu_upload);
209                 importkey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
210                         public boolean onMenuItemClick(MenuItem item) {
211                                 Uri sdcard = Uri.fromFile(Environment.getExternalStorageDirectory());
212                                 String pickerTitle = getString(R.string.pubkey_list_pick);
213
214                                 // Try to use OpenIntent's file browser to pick a file
215                                 Intent intent = new Intent(FileManagerIntents.ACTION_PICK_FILE);
216                                 intent.setData(sdcard);
217                                 intent.putExtra(FileManagerIntents.EXTRA_TITLE, pickerTitle);
218                                 intent.putExtra(FileManagerIntents.EXTRA_BUTTON_TEXT, getString(android.R.string.ok));
219
220                                 try {
221                                         startActivityForResult(intent, REQUEST_CODE_PICK_FILE);
222                                 } catch (ActivityNotFoundException e) {
223                                         // If OI didn't work, try AndExplorer
224                                         intent = new Intent(Intent.ACTION_PICK);
225                                         intent.setDataAndType(sdcard, MIME_TYPE_ANDEXPLORER_FILE);
226                                         intent.putExtra(ANDEXPLORER_TITLE, pickerTitle);
227
228                                         try {
229                                                 startActivityForResult(intent, REQUEST_CODE_PICK_FILE);
230                                         } catch (ActivityNotFoundException e1) {
231                                                 pickFileSimple();
232                                         }
233                                 }
234
235                                 return true;
236                         }
237                 });
238
239                 return true;
240         }
241
242         protected void handleAddKey(final PubkeyBean pubkey) {
243                 if (pubkey.isEncrypted()) {
244                         final View view = inflater.inflate(R.layout.dia_password, null);
245                         final EditText passwordField = (EditText)view.findViewById(android.R.id.text1);
246
247                         new AlertDialog.Builder(PubkeyListActivity.this)
248                                 .setView(view)
249                                 .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() {
250                                         public void onClick(DialogInterface dialog, int which) {
251                                                 handleAddKey(pubkey, passwordField.getText().toString());
252                                         }
253                                 })
254                                 .setNegativeButton(android.R.string.cancel, null).create().show();
255                 } else {
256                         handleAddKey(pubkey, null);
257                 }
258         }
259
260         protected void handleAddKey(PubkeyBean pubkey, String password) {
261                 Object trileadKey = null;
262                 if(PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType())) {
263                         // load specific key using pem format
264                         try {
265                                 trileadKey = PEMDecoder.decode(new String(pubkey.getPrivateKey()).toCharArray(), password);
266                         } catch(Exception e) {
267                                 String message = getResources().getString(R.string.pubkey_failed_add, pubkey.getNickname());
268                                 Log.e(TAG, message, e);
269                                 Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG);
270                         }
271
272                 } else {
273                         // load using internal generated format
274                         PrivateKey privKey = null;
275                         PublicKey pubKey = null;
276                         try {
277                                 privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType(), password);
278                                 pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType());
279                         } catch (Exception e) {
280                                 String message = getResources().getString(R.string.pubkey_failed_add, pubkey.getNickname());
281                                 Log.e(TAG, message, e);
282                                 Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG);
283                                 return;
284                         }
285
286                         // convert key to trilead format
287                         trileadKey = PubkeyUtils.convertToTrilead(privKey, pubKey);
288                         Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey));
289                 }
290
291                 if(trileadKey == null) return;
292
293                 Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname()));
294
295                 // save this key in memory
296                 bound.addKey(pubkey, trileadKey);
297
298                 updateHandler.sendEmptyMessage(-1);
299         }
300
301         @Override
302         public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
303                 // Create menu to handle deleting and editing pubkey
304                 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
305                 final PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(info.position);
306
307                 menu.setHeaderTitle(pubkey.getNickname());
308
309                 // TODO: option load/unload key from in-memory list
310                 // prompt for password as needed for passworded keys
311
312                 // cant change password or clipboard imported keys
313                 final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
314                 final boolean loaded = bound.isKeyLoaded(pubkey.getNickname());
315
316                 MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load);
317                 load.setOnMenuItemClickListener(new OnMenuItemClickListener() {
318                         public boolean onMenuItemClick(MenuItem item) {
319                                 if(loaded) {
320                                         bound.removeKey(pubkey.getNickname());
321                                         updateHandler.sendEmptyMessage(-1);
322                                 } else {
323                                         handleAddKey(pubkey);
324                                         //bound.addKey(nickname, trileadKey);
325                                 }
326                                 return true;
327                         }
328                 });
329
330                 onstartToggle = menu.add(R.string.pubkey_load_on_start);
331                 onstartToggle.setEnabled(!pubkey.isEncrypted());
332                 onstartToggle.setCheckable(true);
333                 onstartToggle.setChecked(pubkey.isStartup());
334                 onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() {
335                         public boolean onMenuItemClick(MenuItem item) {
336                                 // toggle onstart status
337                                 pubkey.setStartup(!pubkey.isStartup());
338                                 pubkeydb.savePubkey(pubkey);
339                                 updateHandler.sendEmptyMessage(-1);
340                                 return true;
341                         }
342                 });
343
344                 MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public);
345                 copyPublicToClipboard.setEnabled(!imported);
346                 copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
347                         public boolean onMenuItemClick(MenuItem item) {
348                                 try {
349                                         PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType());
350                                         String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname());
351
352                                         clipboard.setText(openSSHPubkey);
353                                 } catch (Exception e) {
354                                         e.printStackTrace();
355                                 }
356                                 return true;
357                         }
358                 });
359
360                 MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private);
361                 copyPrivateToClipboard.setEnabled(!pubkey.isEncrypted() || imported);
362                 copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
363                         public boolean onMenuItemClick(MenuItem item) {
364                                 try {
365                                         String data = null;
366
367                                         if (imported)
368                                                 data = new String(pubkey.getPrivateKey());
369                                         else {
370                                                 PrivateKey pk = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType());
371                                                 data = PubkeyUtils.exportPEM(pk, null);
372                                         }
373
374                                         clipboard.setText(data);
375                                 } catch (Exception e) {
376                                         e.printStackTrace();
377                                 }
378                                 return true;
379                         }
380                 });
381
382                 MenuItem changePassword = menu.add(R.string.pubkey_change_password);
383                 changePassword.setEnabled(!imported);
384                 changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() {
385                         public boolean onMenuItemClick(MenuItem item) {
386                                 final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false);
387                                 ((TableRow)changePasswordView.findViewById(R.id.old_password_prompt))
388                                         .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE);
389                                 new AlertDialog.Builder(PubkeyListActivity.this)
390                                         .setView(changePasswordView)
391                                         .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() {
392                                                 public void onClick(DialogInterface dialog, int which) {
393                                                         String oldPassword = ((EditText)changePasswordView.findViewById(R.id.old_password)).getText().toString();
394                                                         String password1 = ((EditText)changePasswordView.findViewById(R.id.password1)).getText().toString();
395                                                         String password2 = ((EditText)changePasswordView.findViewById(R.id.password2)).getText().toString();
396
397                                                         if (!password1.equals(password2)) {
398                                                                 new AlertDialog.Builder(PubkeyListActivity.this)
399                                                                         .setMessage(R.string.alert_passwords_do_not_match_msg)
400                                                                         .setPositiveButton(android.R.string.ok, null)
401                                                                         .create().show();
402                                                                 return;
403                                                         }
404
405                                                         try {
406                                                                 if (!pubkey.changePassword(oldPassword, password1))
407                                                                         new AlertDialog.Builder(PubkeyListActivity.this)
408                                                                                 .setMessage(R.string.alert_wrong_password_msg)
409                                                                                 .setPositiveButton(android.R.string.ok, null)
410                                                                                 .create().show();
411                                                                 else {
412                                                                         pubkeydb.savePubkey(pubkey);
413                                                                         updateHandler.sendEmptyMessage(-1);
414                                                                 }
415                                                         } catch (Exception e) {
416                                                                 Log.e(TAG, "Could not change private key password", e);
417                                                                 new AlertDialog.Builder(PubkeyListActivity.this)
418                                                                         .setMessage(R.string.alert_key_corrupted_msg)
419                                                                         .setPositiveButton(android.R.string.ok, null)
420                                                                         .create().show();
421                                                         }
422                                                 }
423                                         })
424                                         .setNegativeButton(android.R.string.cancel, null).create().show();
425
426                         return true;
427                         }
428                 });
429
430                 confirmUse = menu.add(R.string.pubkey_confirm_use);
431                 confirmUse.setCheckable(true);
432                 confirmUse.setChecked(pubkey.isConfirmUse());
433                 confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() {
434                         public boolean onMenuItemClick(MenuItem item) {
435                                 // toggle confirm use
436                                 pubkey.setConfirmUse(!pubkey.isConfirmUse());
437                                 pubkeydb.savePubkey(pubkey);
438                                 updateHandler.sendEmptyMessage(-1);
439                                 return true;
440                         }
441                 });
442
443                 MenuItem delete = menu.add(R.string.pubkey_delete);
444                 delete.setOnMenuItemClickListener(new OnMenuItemClickListener() {
445                         public boolean onMenuItemClick(MenuItem item) {
446                                 // prompt user to make sure they really want this
447                                 new AlertDialog.Builder(PubkeyListActivity.this)
448                                         .setMessage(getString(R.string.delete_message, pubkey.getNickname()))
449                                         .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() {
450                                                 public void onClick(DialogInterface dialog, int which) {
451
452                                                         // dont forget to remove from in-memory
453                                                         if(loaded)
454                                                                 bound.removeKey(pubkey.getNickname());
455
456                                                         // delete from backend database and update gui
457                                                         pubkeydb.deletePubkey(pubkey);
458                                                         updateHandler.sendEmptyMessage(-1);
459                                                 }
460                                         })
461                                         .setNegativeButton(R.string.delete_neg, null).create().show();
462
463                                 return true;
464                         }
465                 });
466
467         }
468
469
470         protected Handler updateHandler = new Handler() {
471                 @Override
472                 public void handleMessage(Message msg) {
473                         updateList();
474                 }
475         };
476
477         protected void updateList() {
478                 if (pubkeydb == null) return;
479
480                 pubkeys = pubkeydb.allPubkeys();
481                 PubkeyAdapter adapter = new PubkeyAdapter(this, pubkeys);
482
483                 this.setListAdapter(adapter);
484         }
485
486         @Override
487         protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
488                 super.onActivityResult(requestCode, resultCode, intent);
489
490                 switch (requestCode) {
491                 case REQUEST_CODE_PICK_FILE:
492                         if (resultCode == RESULT_OK && intent != null) {
493                                 Uri uri = intent.getData();
494                                 try {
495                                         if (uri != null) {
496                                                 readKeyFromFile(new File(URI.create(uri.toString())));
497                                         } else {
498                                                 String filename = intent.getDataString();
499                                                 if (filename != null)
500                                                         readKeyFromFile(new File(URI.create(filename)));
501                                         }
502                                 } catch (IllegalArgumentException e) {
503                                         Log.e(TAG, "Couldn't read from picked file", e);
504                                 }
505                         }
506                         break;
507                 }
508         }
509
510         /**
511          * @param name
512          */
513         private void readKeyFromFile(File file) {
514                 PubkeyBean pubkey = new PubkeyBean();
515
516                 // find the exact file selected
517                 pubkey.setNickname(file.getName());
518
519                 if (file.length() > MAX_KEYFILE_SIZE) {
520                         Toast.makeText(PubkeyListActivity.this,
521                                         R.string.pubkey_import_parse_problem,
522                                         Toast.LENGTH_LONG).show();
523                         return;
524                 }
525
526                 // parse the actual key once to check if its encrypted
527                 // then save original file contents into our database
528                 try {
529                         byte[] raw = readRaw(file);
530
531                         String data = new String(raw);
532                         if (data.startsWith(PubkeyUtils.PKCS8_START)) {
533                                 int start = data.indexOf(PubkeyUtils.PKCS8_START) + PubkeyUtils.PKCS8_START.length();
534                                 int end = data.indexOf(PubkeyUtils.PKCS8_END);
535
536                                 if (end > start) {
537                                         char[] encoded = data.substring(start, end - 1).toCharArray();
538                                         Log.d(TAG, "encoded: " + new String(encoded));
539                                         byte[] decoded = Base64.decode(encoded);
540
541                                         KeyPair kp = PubkeyUtils.recoverKeyPair(decoded);
542
543                                         pubkey.setType(kp.getPrivate().getAlgorithm());
544                                         pubkey.setPrivateKey(kp.getPrivate().getEncoded());
545                                         pubkey.setPublicKey(kp.getPublic().getEncoded());
546                                 } else {
547                                         Log.e(TAG, "Problem parsing PKCS#8 file; corrupt?");
548                                         Toast.makeText(PubkeyListActivity.this,
549                                                         R.string.pubkey_import_parse_problem,
550                                                         Toast.LENGTH_LONG).show();
551                                 }
552                         } else {
553                                 PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray());
554                                 pubkey.setEncrypted(PEMDecoder.isPEMEncrypted(struct));
555                                 pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED);
556                                 pubkey.setPrivateKey(raw);
557                         }
558
559                         // write new value into database
560                         if (pubkeydb == null)
561                                 pubkeydb = new PubkeyDatabase(this);
562                         pubkeydb.savePubkey(pubkey);
563
564                         updateHandler.sendEmptyMessage(-1);
565                 } catch(Exception e) {
566                         Log.e(TAG, "Problem parsing imported private key", e);
567                         Toast.makeText(PubkeyListActivity.this, R.string.pubkey_import_parse_problem, Toast.LENGTH_LONG).show();
568                 }
569         }
570
571         /**
572          *
573          */
574         private void pickFileSimple() {
575                 // build list of all files in sdcard root
576                 final File sdcard = Environment.getExternalStorageDirectory();
577                 Log.d(TAG, sdcard.toString());
578
579                 // Don't show a dialog if the SD card is completely absent.
580                 final String state = Environment.getExternalStorageState();
581                 if (!Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)
582                                 && !Environment.MEDIA_MOUNTED.equals(state)) {
583                         new AlertDialog.Builder(PubkeyListActivity.this)
584                                 .setMessage(R.string.alert_sdcard_absent)
585                                 .setNegativeButton(android.R.string.cancel, null).create().show();
586                         return;
587                 }
588
589                 List<String> names = new LinkedList<String>();
590                 {
591                         File[] files = sdcard.listFiles();
592                         if (files != null) {
593                                 for(File file : sdcard.listFiles()) {
594                                         if(file.isDirectory()) continue;
595                                         names.add(file.getName());
596                                 }
597                         }
598                 }
599                 Collections.sort(names);
600
601                 final String[] namesList = names.toArray(new String[] {});
602                 Log.d(TAG, names.toString());
603
604                 // prompt user to select any file from the sdcard root
605                 new AlertDialog.Builder(PubkeyListActivity.this)
606                         .setTitle(R.string.pubkey_list_pick)
607                         .setItems(namesList, new OnClickListener() {
608                                 public void onClick(DialogInterface arg0, int arg1) {
609                                         String name = namesList[arg1];
610
611                                         readKeyFromFile(new File(sdcard, name));
612                                 }
613                         })
614                         .setNegativeButton(android.R.string.cancel, null).create().show();
615         }
616
617         class PubkeyAdapter extends ArrayAdapter<PubkeyBean> {
618                 private List<PubkeyBean> pubkeys;
619
620                 class ViewHolder {
621                         public TextView nickname;
622                         public TextView caption;
623                         public ImageView icon;
624                 }
625
626                 public PubkeyAdapter(Context context, List<PubkeyBean> pubkeys) {
627                         super(context, R.layout.item_pubkey, pubkeys);
628
629                         this.pubkeys = pubkeys;
630                 }
631
632                 @Override
633                 public View getView(int position, View convertView, ViewGroup parent) {
634                         ViewHolder holder;
635
636                         if (convertView == null) {
637                                 convertView = inflater.inflate(R.layout.item_pubkey, null, false);
638
639                                 holder = new ViewHolder();
640
641                                 holder.nickname = (TextView) convertView.findViewById(android.R.id.text1);
642                                 holder.caption = (TextView) convertView.findViewById(android.R.id.text2);
643                                 holder.icon = (ImageView) convertView.findViewById(android.R.id.icon1);
644
645                                 convertView.setTag(holder);
646                         } else
647                                 holder = (ViewHolder) convertView.getTag();
648
649                         PubkeyBean pubkey = pubkeys.get(position);
650                         holder.nickname.setText(pubkey.getNickname());
651
652                         boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
653
654                         if (imported) {
655                                 try {
656                                         PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray());
657                                         String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA";
658                                         holder.caption.setText(String.format("%s unknown-bit", type));
659                                 } catch (IOException e) {
660                                         Log.e(TAG, "Error decoding IMPORTED public key at " + pubkey.getId(), e);
661                                 }
662                         } else {
663                                 try {
664                                         PublicKey pub = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType());
665                                         holder.caption.setText(PubkeyUtils.describeKey(pub, pubkey.isEncrypted()));
666                                 } catch (Exception e) {
667                                         Log.e(TAG, "Error decoding public key at " + pubkey.getId(), e);
668                                         holder.caption.setText(R.string.pubkey_unknown_format);
669                                 }
670                         }
671
672                         if (bound == null) {
673                                 holder.icon.setVisibility(View.GONE);
674                         } else {
675                                 holder.icon.setVisibility(View.VISIBLE);
676
677                                 if (bound.isKeyLoaded(pubkey.getNickname()))
678                                         holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true);
679                                 else
680                                         holder.icon.setImageState(new int[] {  }, true);
681                         }
682
683                         return convertView;
684                 }
685         }
686 }