import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-import android.content.Context;
-import android.content.SharedPreferences;
import android.net.Uri;
import com.google.android.apps.authenticator.Base32String;
private long counter;
private int period;
- public static List<Token> getTokens(Context ctx) {
- SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
-
- List<Token> tokens = new ArrayList<Token>();
- for (String key : prefs.getAll().keySet()) {
- try {
- tokens.add(new Token(prefs.getString(key, null)));
- } catch (TokenUriInvalidException e) {
- e.printStackTrace();
- }
- }
-
- return tokens;
- }
-
private Token(Uri uri) throws TokenUriInvalidException {
if (!uri.getScheme().equals("otpauth"))
throw new TokenUriInvalidException();
this(Uri.parse(uri));
}
- private String getId() {
+ public String getID() {
String id;
if (issuerInt != null && !issuerInt.equals(""))
id = issuerInt + ":" + label;
return id;
}
- public void remove(Context ctx) {
- SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
- prefs.edit().remove(getId()).apply();
+ public String getIssuer() {
+ return issuerExt != null ? issuerExt : "";
}
- public void save(Context ctx) {
- SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
- prefs.edit().putString(getId(), toString()).apply();
+ public String getLabel() {
+ return label != null ? label : "";
}
- public String getTitle() {
- String title = "";
- if (issuerExt != null && !issuerExt.equals(""))
- title += issuerExt + ": ";
- title += label;
- return title;
+ public String getCode() {
+ switch (type) {
+ case HOTP:
+ return getHOTP(counter);
+ case TOTP:
+ return getHOTP(System.currentTimeMillis() / 1000 / period);
+ }
+
+ return null;
}
- public String getCurrentTokenValue(Context ctx, boolean increment) {
- if (type == TokenType.HOTP) {
- if (increment) {
- try {
- return getHOTP(counter++);
- } finally {
- save(ctx);
- }
- } else {
- String placeholder = "";
- for (int i = 0; i < digits; i++)
- placeholder += "-";
-
- return placeholder;
- }
- }
+ public String getPlaceholder() {
+ StringBuilder sb = new StringBuilder(digits);
+ for (int i = 0; i < digits; i++)
+ sb.append('-');
+ return sb.toString();
+ }
- return getHOTP(System.currentTimeMillis() / 1000 / period);
+ public void increment() {
+ if (type == TokenType.HOTP)
+ counter++;
}
public Uri toUri() {
package org.fedorahosted.freeotp;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
}
}
- private final List<Token> tokens = new ArrayList<Token>();
- private final Ticker ticker = new Ticker();
-
- private void sort() {
- Collections.sort(tokens, new Comparator<Token>() {
- @Override
- public int compare(Token lhs, Token rhs) {
- return lhs.getTitle().compareTo(rhs.getTitle());
- }
- });
+ private static class ViewHolder {
+ int index;
+ Token token;
+ TextView code;
+ TextView title;
}
+ private final TokenStore ts;
+ private final Ticker ticker = new Ticker();
+
public TokenAdapter(Context ctx) {
- tokens.addAll(Token.getTokens(ctx));
+ ts = new TokenStore(ctx);
ticker.sendEmptyMessageDelayed(0, 200);
- sort();
}
@Override
public int getCount() {
- return tokens.size();
+ return ts.getTokenCount();
}
@Override
public Token getItem(int position) {
- return tokens.get(position);
+ return ts.get(position);
}
@Override
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- final Context ctx = parent.getContext();
+ Token token = ts.get(position);
- if (convertView == null) {
- switch (getItem(position).getType()) {
- case HOTP:
- convertView = View.inflate(ctx, R.layout.hotp, null);
- break;
+ if (convertView == null)
+ convertView = newView(parent.getContext(), token.getType(), parent);
- case TOTP:
- convertView = View.inflate(ctx, R.layout.totp, null);
- break;
- }
- }
+ bindView(parent.getContext(), convertView, token, position);
+ return convertView;
+ }
- final Token item = getItem(position);
- final TextView code = (TextView) convertView.findViewById(R.id.code);
- final TextView title = (TextView) convertView.findViewById(R.id.title);
- final ImageButton ib = (ImageButton) convertView.findViewById(R.id.button);
+ public void bindView(Context ctx, View view, Token token, int position) {
+ ViewHolder holder = (ViewHolder) view.getTag();
+ holder.index = position;
+ holder.token = token;
- code.setText(item.getCurrentTokenValue(ctx, false));
- title.setText(item.getTitle());
+ holder.code.setText(token.getPlaceholder());
+ holder.title.setText(token.getIssuer() + ": " + token.getLabel());
+ }
- ib.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- String delmsg = ctx.getString(R.string.delete_message);
-
- AlertDialog ad = new AlertDialog.Builder(ctx)
- .setTitle("Delete")
- .setMessage(delmsg + item.getTitle())
- .setIcon(android.R.drawable.ic_delete)
- .setPositiveButton(R.string.delete,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- tokens.remove(tokens.indexOf(item));
- item.remove(ctx);
- notifyDataSetChanged();
- dialog.dismiss();
- }
-
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- }).create();
- ad.show();
- }
- });
+ public View newView(Context ctx, Token.TokenType type, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ ViewHolder holder = new ViewHolder();
- switch (getItem(position).getType()) {
+ View view = null;
+ switch (type) {
case HOTP:
- ImageButton hotp = (ImageButton) convertView.findViewById(R.id.hotpButton);
+ view = inflater.inflate(R.layout.hotp, parent, false);
+ ImageButton hotp = (ImageButton) view.findViewById(R.id.hotpButton);
+ hotp.setTag(holder);
hotp.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- code.setText(item.getCurrentTokenValue(ctx, true));
+ ViewHolder holder = (ViewHolder) v.getTag();
+ holder.code.setText(holder.token.getCode());
+ holder.token.increment();
+ ts.save(holder.token);
}
});
break;
case TOTP:
- ProgressBar pb = (ProgressBar) convertView.findViewById(R.id.totpProgressBar);
+ view = inflater.inflate(R.layout.totp, parent, false);
+ ProgressBar pb = (ProgressBar) view.findViewById(R.id.totpProgressBar);
+ pb.setTag(holder);
ticker.set(pb, new Ticker.OnTickListener() {
@Override
public void tick(ProgressBar pb) {
- int max = pb.getMax();
- int pro = item.getProgress();
- pb.setProgress(max - pro);
- if (pro < max / 20 || pro > max / 20 * 19)
- code.setText(item.getCurrentTokenValue(ctx, false));
+ ViewHolder holder = (ViewHolder) pb.getTag();
+ pb.setProgress(pb.getMax() - holder.token.getProgress());
+ holder.code.setText(holder.token.getCode());
}
});
break;
}
- return convertView;
+ ImageButton ib = (ImageButton) view.findViewById(R.id.button);
+ ib.setTag(holder);
+ ib.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final ViewHolder holder = (ViewHolder) v.getTag();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(v.getContext().getString(R.string.delete_message));
+ sb.append(holder.token.getIssuer());
+ sb.append("\n");
+ sb.append(holder.token.getLabel());
+
+ AlertDialog ad = new AlertDialog.Builder(v.getContext())
+ .setTitle("Delete")
+ .setMessage(sb.toString())
+ .setIcon(android.R.drawable.ic_delete)
+ .setPositiveButton(R.string.delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ts.del(holder.index);
+ notifyDataSetChanged();
+ dialog.dismiss();
+ }
+
+ })
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ })
+ .create();
+ ad.show();
+ }
+ });
+
+ holder.code = (TextView) view.findViewById(R.id.code);
+ holder.title = (TextView) view.findViewById(R.id.title);
+ view.setTag(holder);
+
+ return view;
}
@Override
case TOTP:
return 1;
default:
- return -1;
+ return IGNORE_ITEM_VIEW_TYPE;
}
}
public void add(Context ctx, String uri) throws TokenUriInvalidException {
- Token t = new Token(uri);
- t.save(ctx);
- tokens.add(t);
- sort();
+ ts.add(new Token(uri));
notifyDataSetChanged();
}
}
--- /dev/null
+package org.fedorahosted.freeotp;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class TokenStore {
+ private static final String NAME = "tokens";
+ private static final String ORDER = "tokenOrder";
+ private SharedPreferences prefs;
+
+ private List<String> getTokenOrder() {
+ try {
+ JSONArray array = new JSONArray(prefs.getString(ORDER, null));
+ List<String> out = new LinkedList<String>();
+ for (int i = 0; i < array.length(); i++)
+ out.add(array.getString(i));
+ return out;
+ } catch (JSONException e) {
+ } catch (NullPointerException e) {
+ }
+
+ return new LinkedList<String>();
+ }
+
+ private SharedPreferences.Editor setTokenOrder(List<String> order) {
+ JSONArray array = new JSONArray();
+ for (String key : order)
+ array.put(key);
+
+ return prefs.edit().putString(ORDER, array.toString());
+ }
+
+ public TokenStore(Context ctx) {
+ prefs = ctx.getApplicationContext()
+ .getSharedPreferences(NAME, Context.MODE_PRIVATE);
+ }
+
+ public void add(Token token) {
+ String key = token.getID();
+
+ if (prefs.contains(key))
+ return;
+
+ List<String> order = getTokenOrder();
+ order.add(0, key);
+ setTokenOrder(order).putString(key, token.toString()).apply();
+ }
+
+ public void del(int index) {
+ List<String> order = getTokenOrder();
+ String key = order.remove(index);
+ setTokenOrder(order).remove(key).apply();
+ }
+
+ public void save(Token token) {
+ prefs.edit().putString(token.getID(), token.toString()).apply();
+ }
+
+ public Token get(int index) {
+ try {
+ return new Token(prefs.getString(getTokenOrder().get(index), null));
+ } catch (TokenUriInvalidException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public int getTokenCount() {
+ return getTokenOrder().size();
+ }
+
+ public void move(int fromIndex, int toIndex) {
+ List<String> order = getTokenOrder();
+ if (fromIndex < 0 || fromIndex > order.size())
+ return;
+ if (toIndex < 0 || toIndex > order.size())
+ return;
+
+ order.add(toIndex, order.remove(fromIndex));
+ setTokenOrder(order).apply();
+ }
+}