]> Pileus Git - ~andy/freeotp/commitdiff
Update storage model to match iOS and permit reordering
authorNathaniel McCallum <npmccallum@redhat.com>
Tue, 26 Nov 2013 21:24:18 +0000 (16:24 -0500)
committerNathaniel McCallum <npmccallum@redhat.com>
Tue, 26 Nov 2013 21:24:18 +0000 (16:24 -0500)
src/org/fedorahosted/freeotp/Token.java
src/org/fedorahosted/freeotp/TokenAdapter.java
src/org/fedorahosted/freeotp/TokenStore.java [new file with mode: 0644]

index b7416c8d17ebfbc4b50ee8fd3b77baf3b4a6b39a..35a9576a1539fc1c88d8caa081b4e73bcba04d93 100644 (file)
@@ -23,15 +23,11 @@ package org.fedorahosted.freeotp;
 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;
@@ -56,21 +52,6 @@ public class Token {
        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();
@@ -195,7 +176,7 @@ public class Token {
                this(Uri.parse(uri));
        }
 
-       private String getId() {
+       public String getID() {
                String id;
                if (issuerInt != null && !issuerInt.equals(""))
                        id = issuerInt + ":" + label;
@@ -207,42 +188,35 @@ public class Token {
                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() {
index 1915548e6bc65268f8438623a9b7abc2f48a2b2f..b0035e5cef5dd704e00454520e27fb5aaf70fdde 100644 (file)
 
 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;
@@ -34,6 +30,7 @@ import android.content.Context;
 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;
@@ -63,32 +60,29 @@ public class TokenAdapter extends BaseAdapter {
                }
        }
 
-       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
@@ -98,86 +92,104 @@ public class TokenAdapter extends BaseAdapter {
 
        @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
@@ -193,15 +205,12 @@ public class TokenAdapter extends BaseAdapter {
                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();
        }
 }
diff --git a/src/org/fedorahosted/freeotp/TokenStore.java b/src/org/fedorahosted/freeotp/TokenStore.java
new file mode 100644 (file)
index 0000000..a9aa560
--- /dev/null
@@ -0,0 +1,92 @@
+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();
+       }
+}