super(context);
setup();
}
-
+
private void setup() {
paint = new Paint();
rectf = new RectF();
rect = new Rect();
-
+
paint.setColor(0x33333300);
paint.setAlpha(0x99);
paint.setAntiAlias(true);
rect.top += getPaddingTop() + 2;
rect.right -= getPaddingRight() + 2;
rect.bottom -= getPaddingBottom() + 2;
-
+
rectf.set(rect);
canvas.drawArc(rectf, -90, getProgress() * 360 / getMax(), true, paint);
}
import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
-import android.net.Uri;
-import android.os.Bundle;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
-
+
menu.findItem(R.id.action_add).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
public boolean onMenuItemClick(MenuItem item) {
Intent i = new Intent(ACTION_SCAN);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
.setTitle(R.string.install_title)
.setMessage(R.string.install_message)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = Uri.parse("market://details?id=" + PROVIDERS.get(0));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialogInterface, int i) {
return;
}
return true;
}
-
- public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_OK) {
try {
ta.add(this, intent.getStringExtra("SCAN_RESULT"));
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-import com.google.android.apps.authenticator.Base32String;
-import com.google.android.apps.authenticator.Base32String.DecodingException;
-
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
+import com.google.android.apps.authenticator.Base32String;
+import com.google.android.apps.authenticator.Base32String.DecodingException;
+
public class Token {
public static class TokenUriInvalidException extends Exception {
private static final long serialVersionUID = -1108624734612362345L;
}
-
+
public static enum TokenType {
HOTP, TOTP
}
-
- private String issuerInt;
- private String issuerExt;
- private String label;
+
+ private final String issuerInt;
+ private final String issuerExt;
+ private final String label;
private TokenType type;
private String algo;
private byte[] key;
private int digits;
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 {
e.printStackTrace();
}
}
-
+
return tokens;
}
private Token(Uri uri) throws TokenUriInvalidException, NoSuchAlgorithmException {
if (!uri.getScheme().equals("otpauth"))
throw new TokenUriInvalidException();
-
+
if (uri.getAuthority().equals("totp"))
type = TokenType.TOTP;
else if (uri.getAuthority().equals("hotp"))
type = TokenType.HOTP;
else
throw new TokenUriInvalidException();
-
+
String path = uri.getPath();
if (path == null)
throw new TokenUriInvalidException();
path = path.substring(1);
if (path.length() == 0)
throw new TokenUriInvalidException();
-
+
int i = path.indexOf(':');
issuerExt = i < 0 ? "" : path.substring(0, i);
issuerInt = uri.getQueryParameter("issuer");
label = path.substring(i >= 0 ? i + 1 : 0);
-
+
algo = uri.getQueryParameter("algorithm");
if (algo == null)
algo = "sha1";
!algo.equals("SHA512") && !algo.equals("MD5"))
throw new TokenUriInvalidException();
Mac.getInstance("Hmac" + algo);
-
+
try {
String d = uri.getQueryParameter("digits");
if (d == null)
} catch (NumberFormatException e) {
throw new TokenUriInvalidException();
}
-
+
switch (type) {
case HOTP:
try {
}
break;
}
-
+
try {
String s = uri.getQueryParameter("secret");
key = Base32String.decode(s);
throw new TokenUriInvalidException();
}
}
-
+
private String getHOTP(long counter) {
// Encode counter in network byte order
ByteBuffer bb = ByteBuffer.allocate(8);
bb.putLong(counter);
-
+
// Create digits divisor
int div = 1;
for (int i = digits; i > 0; i--)
div *= 10;
-
+
// Create the HMAC
try {
Mac mac = Mac.getInstance("Hmac" + algo);
mac.init(new SecretKeySpec(key, "Hmac" + algo));
-
+
// Do the hashing
byte[] digest = mac.doFinal(bb.array());
-
+
// Truncate
int binary;
int off = digest[digest.length - 1] & 0xf;
binary |= (digest[off + 2] & 0xff) << 0x08;
binary |= (digest[off + 3] & 0xff) << 0x00;
binary = binary % div;
-
+
// Zero pad
String hotp = Integer.toString(binary);
while (hotp.length() != digits)
hotp = "0" + hotp;
-
+
return hotp;
} catch (InvalidKeyException e) {
e.printStackTrace();
return "";
}
-
+
public Token(String uri) throws TokenUriInvalidException, NoSuchAlgorithmException {
this(Uri.parse(uri));
}
-
+
private String getId() {
String id;
if (issuerInt != null && !issuerInt.equals(""))
id = issuerExt + ":" + label;
else
id = label;
-
+
return id;
}
-
+
public void remove(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
prefs.edit().remove(getId()).apply();
}
-
+
public void save(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
prefs.edit().putString(getId(), toString()).apply();
}
-
+
public String getTitle() {
String title = "";
if (issuerExt != null && !issuerExt.equals(""))
title += label;
return title;
}
-
+
public String getCurrentTokenValue(Context ctx, boolean increment) {
if (type == TokenType.HOTP) {
if (increment) {
String placeholder = "";
for (int i = 0; i < digits; i++)
placeholder += "-";
-
+
return placeholder;
}
}
-
+
return getHOTP(System.currentTimeMillis() / 1000 / period);
}
-
+
public Uri toUri() {
String issuerLabel = !issuerExt.equals("") ? issuerExt + ":" + label : label;
-
+
Uri.Builder builder = new Uri.Builder()
.scheme("otpauth")
.path(issuerLabel)
.appendQueryParameter("issuer", issuerInt == null ? issuerExt : issuerInt)
.appendQueryParameter("algorithm", algo)
.appendQueryParameter("digits", Integer.toString(digits));
-
+
switch (type) {
case HOTP:
builder.authority("hotp");
builder.appendQueryParameter("period", Integer.toString(period));
break;
}
-
+
return builder.build();
}
-
+
public TokenType getType() {
return type;
}
-
+
// Progress is on a scale from 0 - 1000.
public int getProgress() {
int p = period * 10;
-
+
long time = System.currentTimeMillis() / 100;
return (int) ((time % p) * 1000 / p);
}
-
+
@Override
public String toString() {
return toUri().toString();
private static interface OnTickListener {
public void tick(ProgressBar pb);
}
-
- private Map<ProgressBar, OnTickListener> map = new HashMap<ProgressBar, OnTickListener>();
-
+
+ private final Map<ProgressBar, OnTickListener> map = new HashMap<ProgressBar, OnTickListener>();
+
@Override
public void handleMessage(Message msg) {
for (ProgressBar pb : map.keySet())
map.get(pb).tick(pb);
-
+
sendEmptyMessageDelayed(0, 200);
}
-
+
public void set(ProgressBar pb, OnTickListener otl) {
map.put(pb, otl);
}
}
-
- private List<Token> tokens = new ArrayList<Token>();
- private Ticker ticker = new Ticker();
-
+
+ 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());
}
});
}
-
+
public TokenAdapter(Context ctx) {
tokens.addAll(Token.getTokens(ctx));
ticker.sendEmptyMessageDelayed(0, 200);
sort();
}
-
+
@Override
public int getCount() {
return tokens.size();
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Context ctx = parent.getContext();
-
+
if (convertView == null) {
switch (getItem(position).getType()) {
case HOTP:
convertView = View.inflate(ctx, R.layout.hotp, null);
break;
-
+
case TOTP:
convertView = View.inflate(ctx, R.layout.totp, null);
break;
}
}
-
+
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);
-
+
code.setText(item.getCurrentTokenValue(ctx, false));
title.setText(item.getTitle());
-
+
ib.setOnClickListener(new OnClickListener() {
+ @Override
public void onClick(View v) {
String delmsg = ctx.getString(R.string.delete_message);
.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();
}
case HOTP:
ImageButton hotp = (ImageButton) convertView.findViewById(R.id.hotpButton);
hotp.setOnClickListener(new OnClickListener() {
+ @Override
public void onClick(View v) {
code.setText(item.getCurrentTokenValue(ctx, true));
}
});
break;
-
+
case TOTP:
ProgressBar pb = (ProgressBar) convertView.findViewById(R.id.totpProgressBar);
ticker.set(pb, new Ticker.OnTickListener() {
+ @Override
public void tick(ProgressBar pb) {
int max = pb.getMax();
int pro = item.getProgress();
});
break;
}
-
+
return convertView;
}
-
+
@Override
public int getViewTypeCount() {
return 2;
}
-
+
@Override
public int getItemViewType(int position) {
switch (getItem(position).getType()) {
return -1;
}
}
-
+
public void add(Context ctx, String uri) throws NoSuchAlgorithmException, TokenUriInvalidException {
Token t = new Token(uri);
t.save(ctx);