由于个人无法管理大量的用户名和密码,而又不敢随便下载一些现成的app来管理这样的敏感数据,抱着既为了学习又为了使用的态度开发了这个简易的密码仓库安卓应用。
主要功能如下:
1、访问密码仓库应用的手势锁创建、手势锁验证、手势锁重置
2、账号与密码的添加、修改、删除、查看与搜索
3、密码(包括手势锁密码与添加的账号密码)的加密解密
截图如下:
列表中上面是账号信息下面是账号名,点击条目后会toast弹出密码的明文来,当然要进入这个界面得先通过手势锁验证。
大体应用的逻辑如下:
1、启动后判断用户是否已设置手势锁,没有则跳转至设置界面,若已设置则跳转至手势锁验证界面;
主要代码:
public class LaucherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences repositorySP=getSharedPreferences(Constants.SP_NAME, MODE_PRIVATE);
boolean initialed=repositorySP.getBoolean(Constants.SP_INITIALED, false);
if(initialed){
Intent i=new Intent(this,GesturelockCheckActivity.class);
startActivity(i);
}else{
Intent i=new Intent(this,GesturelockSetActivity.class);
startActivity(i);
}
initImageLoader(this);
finish();
}
2、手势锁设置的activity——主要使用了一个GestureLockViewGroup的组件,这个组件是github上下的,大家可以去网上下或者在我随后放出的项目里找,这个组件最核心的使用方法是一个setOnGestureLockViewListener,这个监听器有三个回调方法,分别是onGestureEvent(boolean matched) 对应用户一次完整的手势输入,传过来的参数代表用户此次手势是否与设定的密码匹配、onBlockSelected(int cId)对应用户在手势过程中手指移动一个圆块后事件,cId是这个圆块的数字id、onUnmatchedExceedBoundary()对应件用户尝试密码次数超过边界后的回调事件。利用这三个方法我们就可以完成手势锁密码的设置、验证、尝试过多处理等功能。手势锁设置后,我将密码数组以JsonArray保存并用AES加密放到SharedPreference中,源码如下:
public class GesturelockSetActivity extends Activity {
private GestureLockViewGroup gesturelock;
private List<Integer> gesturePwdList;
private boolean confirm=false;
private TextView description;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gesturelock_set);
description=(TextView) findViewById(R.id.tv_description);
description.setText("设置手势密码");
gesturelock=(GestureLockViewGroup) findViewById(R.id.gesturelock);
gesturePwdList=new ArrayList<Integer>();
gesturelock.setOnGestureLockViewListener(new OnGestureLockViewListener() {
@Override
public void onUnmatchedExceedBoundary() {
}
@Override
public void onGestureEvent(boolean matched) {
if(!confirm){
confirm=true;
ToastUtils.show(GesturelockSetActivity.this, "再输一次");
gesturelock.setAnswer(convertList(gesturePwdList));
gesturePwdList.clear();
}else if(matched){
gesturelock.setAnswer(convertList(gesturePwdList));
ToastUtils.show(GesturelockSetActivity.this, "手势密码设置成功");
//TODO 持久化密码
saveLock();
Intent i=new Intent(GesturelockSetActivity.this,PwdListActivity.class);
startActivity(i);
finish();
}else{
confirm=false;
ToastUtils.show(GesturelockSetActivity.this, "两次设置不匹配,请重新设置");
gesturePwdList.clear();
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
gesturelock.reset();
}
}, 300);
}
private int[] convertList(List<Integer> gesturePwdList) {
int[] pwd=new int[gesturePwdList.size()];
int i=0;
for(i=0;i<pwd.length;i++){
pwd[i]=gesturePwdList.get(i);
}
return pwd;
}
@Override
public void onBlockSelected(int cId) {
gesturePwdList.add(cId);
}
});
}
/**
* 持久化手势锁密码
*/
protected void saveLock() {
JSONArray jarr=new JSONArray();
for(int n:gesturePwdList){
jarr.put(n);
}
SharedPreferences repository=getSharedPreferences(Constants.SP_NAME, MODE_PRIVATE);
Editor editor=repository.edit();
editor.putString(Constants.SP_GESTURELOCK, AES.enc(jarr.toString()));
editor.putBoolean(Constants.SP_INITIALED, true);
editor.commit();
}
}
3、手势锁验证的activity——这个和手势锁设置类似,用来处理手势锁验证与手势锁重置的,大家如果理解了手势锁的设置应该不难看明白验证了~源码如下:
public class GesturelockCheckActivity extends Activity {
private GestureLockViewGroup gesturelock;
private TextView description;
private boolean reset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gesturelock_set);
description=(TextView) findViewById(R.id.tv_description);
description.setText("手势验证");
gesturelock=(GestureLockViewGroup) findViewById(R.id.gesturelock);
int[] pwd=getGesturePwd();
gesturelock.setAnswer(pwd);
gesturelock.setOnGestureLockViewListener(new MgestureLockListener());
reset=getIntent().getBooleanExtra("reset", false);
}
/** 从sharedpreference中提取出保存的用户手势密码
* @return
*/
private int[] getGesturePwd() {
int[] pwd = null;
SharedPreferences repository=getSharedPreferences(Constants.SP_NAME,MODE_PRIVATE);
String shadowedJarr=repository.getString(Constants.SP_GESTURELOCK, "");
try {
JSONArray jarr=new JSONArray(AES.dec(shadowedJarr));
pwd=new int[jarr.length()];
for(int i=0;i<jarr.length();i++){
pwd[i]=jarr.getInt(i);
}
} catch (JSONException e) {
Log.e("json err", e.getLocalizedMessage());
}
return pwd;
}
private class MgestureLockListener implements OnGestureLockViewListener{
@Override
public void onBlockSelected(int position) {
}
@Override
public void onGestureEvent(boolean matched) {
if(matched&&!reset){
Intent i=new Intent(GesturelockCheckActivity.this,PwdListActivity.class);
startActivity(i);
finish();
}else if(matched&&reset){
clearLock();
Intent i=new Intent(GesturelockCheckActivity.this,GesturelockSetActivity.class);
startActivity(i);
finish();
}else{
ToastUtils.show(GesturelockCheckActivity.this, "手势错误");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
gesturelock.reset();
}
}, 500);
}
}
@Override
public void onUnmatchedExceedBoundary() {
ToastUtils.show(GesturelockCheckActivity.this, "连续密码输入错误,请稍后再试");
gesturelock.setVisibility(View.GONE);;
new Handler().postDelayed(new Runnable(){
@Override
public void run(){
gesturelock.setVisibility(View.VISIBLE);
}
}, 5000);
}
}
public void clearLock() {
Editor editor=getSharedPreferences(Constants.SP_NAME, MODE_PRIVATE).edit();
editor.putBoolean(Constants.SP_INITIALED, false);
editor.putString(Constants.SP_GESTURELOCK, "");
editor.commit();
}
}
4、账号列表页——这个页面主要是从本地数据库读入账号信息,并以Listview呈现出来,当点击listviewitem后,会查找对应的账号密码密文,将其解密后toast出明文给用户。此页面上也可以添加新账户信息,搜索账号信息,长按item编辑、删除条目。这里数据库操作我使用了一个orm框架GreenDAO,感兴趣的同学可以搜索一下这类框架的用法,搜索部分我使用了安卓提供的搜索接口,朋友们可以参考安卓官方文档中UserInterface-Search下的内容。源码如下:
public class PwdListActivity extends Activity implements OnClickListener {
private ListView listv;
private BaseAdapter adapter;
private List<account> accountlist;
private PopupWindow pop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pwdlist);
listv = (ListView) findViewById(R.id.listview);
listv.setOnItemClickListener(new MonItemClickListener());
listv.setOnItemLongClickListener(new MLongClickListener());
}
@Override
protected void onResume() {
accountDao aDao = DAOUtils.getAccountDao(this);
accountlist = aDao.queryBuilder().list();
adapter = new PwdAdapter();
listv.setAdapter(adapter);
super.onResume();
}
private class PwdAdapter extends BaseAdapter {
@Override
public int getCount() {
return accountlist.size();
}
@Override
public Object getItem(int position) {
return accountlist.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View cv, ViewGroup group) {
ViewHolder holder = null;
account acc = accountlist.get(position);
if (cv != null) {
holder = (ViewHolder) cv.getTag();
} else {
cv = LayoutInflater.from(PwdListActivity.this).inflate(
R.layout.listitem, null);
holder = new ViewHolder();
holder.img = (ImageView) cv.findViewById(R.id.img);
holder.tv_name = (TextView) cv.findViewById(R.id.tv_title);
holder.tv_account = (TextView) cv.findViewById(R.id.tv_account);
cv.setTag(holder);
}
try {
Log.d("pwdlist", acc.getImguri() + " " + acc.getProvider()
+ " ");
if(!TextUtils.isEmpty(acc.getImguri()))
ImageLoader.getInstance().displayImage(acc.getImguri(),
holder.img);
else
holder.img.setImageResource(R.drawable.ic_launcher);
holder.tv_name.setText(acc.getProvider());
holder.tv_account.setText(acc.getUname());
} catch (Exception e) {
}
return cv;
}
private class ViewHolder {
ImageView img;
TextView tv_name, tv_account;
}
}
private class MonItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> listv, View v, int position,
long id) {
account acc = (account) listv.getAdapter().getItem(position);
String shadow = acc.getShadow();
Log.d("shadow pwd", shadow);
ToastUtils.show(PwdListActivity.this, AES.dec(shadow));
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.add:
Intent i = new Intent(this, EditAccoutActivity.class);
startActivity(i);
break;
case R.id.tvedit:
Intent inedit = new Intent(this, EditAccoutActivity.class);
inedit.putExtra("edit", (Long) v.getTag());
startActivity(inedit);
pop.dismiss();
break;
case R.id.tvdel:
account acc=(account) v.getTag();
Builder builder = new AlertDialog.Builder(this);
builder.setTitle("确定删除?").setMessage("点击确定删除该账户信息")
.setPositiveButton("确定", new MPositiveOnClickListener(acc))
.setNegativeButton("取消", null);
builder.create().show();
pop.dismiss();
break;
case R.id.tvview:
String shadow = (String) v.getTag();
ToastUtils.show(PwdListActivity.this, AES.dec(shadow));
pop.dismiss();
break;
case R.id.reset_gesture:
Intent reset=new Intent(this,GesturelockCheckActivity.class);
reset.putExtra("reset", true);
startActivity(reset);
finish();
}
}
private class MLongClickListener implements OnItemLongClickListener {
@Override
public boolean onItemLongClick(AdapterView<?> listv, View v,
int position, long id) {
View cv = LayoutInflater.from(getApplicationContext()).inflate(
R.layout.item_popup, null);
TextView edit, del, view;
edit = (TextView) cv.findViewById(R.id.tvedit);
del = (TextView) cv.findViewById(R.id.tvdel);
view = (TextView) cv.findViewById(R.id.tvview);
edit.setOnClickListener(PwdListActivity.this);
del.setOnClickListener(PwdListActivity.this);
view.setOnClickListener(PwdListActivity.this);
account acc = (account) listv.getAdapter().getItem(position);
edit.setTag(acc.getId());
del.setTag(acc);
view.setTag(acc.getShadow());
pop = new PopupWindow(cv, LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
pop.setFocusable(true);
pop.setBackgroundDrawable(getResources().getDrawable(
R.color.gray_background));
pop.setOutsideTouchable(true);
pop.showAsDropDown(v);
return false;
}
}
private class MPositiveOnClickListener implements DialogInterface.OnClickListener{
account acc;
public MPositiveOnClickListener(account acc) {
this.acc=acc;
}
@Override
public void onClick(DialogInterface arg0, int arg1) {
accountDao accDao=DAOUtils.getAccountDao(PwdListActivity.this);
accDao.delete(acc);
accountlist = DAOUtils.getAccountDao(PwdListActivity.this).queryBuilder().list();
adapter = new PwdAdapter();
listv.setAdapter(adapter);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
SearchManager manager=(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchview=(SearchView)menu.findItem(R.id.searchview).getActionView();//XXX
searchview.setSearchableInfo(manager.getSearchableInfo(new ComponentName("com.lttclaw.pwdrepository", "com.lttclaw.pwdrepository.SearchActivity")));
searchview.setOnQueryTextListener(new OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String arg0) {
return false;
}
@Override
public boolean onQueryTextChange(String arg0) {
return false;
}
});
searchview.setSubmitButtonEnabled(true);
return true;
}
}
这些就是应用中最核心的部分了,剩下的像加密类、控件类、常量类大家可以在源码里找到,希望大家能在此找到对自己有用的东西~