由於個人無法管理大量的用戶名和密碼,而又不敢隨便下載一些現成的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;
}
}
這些就是應用中最核心的部分了,剩下的像加密類、控件類、常量類大家可以在源碼裏找到,希望大家能在此找到對自己有用的東西~
源碼下載