net.gaast.giggity.ChooserActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.gaast.giggity.ChooserActivity.java

Source

/*
 * Giggity -- Android app to view conference/festival schedules
 * Copyright 2008-2011 Wilmer van der Gaast <wilmer@gaast.net>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

package net.gaast.giggity;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.InputType;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import net.gaast.giggity.Db.DbSchedule;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Date;
import java.util.zip.DataFormatException;

public class ChooserActivity extends Activity implements SwipeRefreshLayout.OnRefreshListener {
    private Db.Connection db;

    private SwipeRefreshLayout refresher;
    private ListView list;
    private ScheduleAdapter lista;
    private Handler seedRefreshMenu;

    private final String BARCODE_SCANNER = "com.google.zxing.client.android.SCAN";
    private final String BARCODE_ENCODE = "com.google.zxing.client.android.ENCODE";

    private SharedPreferences pref;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //this.setTheme(android.R.style.Theme_Holo);

        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        requestWindowFeature(Window.FEATURE_PROGRESS);

        /*//test stuff
        Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
        long[] pattern = {  };
        v.vibrate(pattern, -1);
        */

        Giggity app = (Giggity) getApplication();
        db = app.getDb();
        pref = PreferenceManager.getDefaultSharedPreferences(app);

        refreshSeed(false);

        list = new ListView(this);
        list.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
                DbSchedule item = (DbSchedule) lista.getItem(position);
                if (item != null) {
                    openSchedule(item, false);
                }
            }
        });
        list.setLongClickable(true);
        list.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
            @Override
            public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
                AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfo;
                DbSchedule sched = (DbSchedule) lista.getItem((int) mi.id);
                if (sched != null) {
                    menu.setHeaderTitle(sched.getTitle());
                    menu.add(ContextMenu.NONE, 0, 0, R.string.refresh);
                    menu.add(ContextMenu.NONE, 3, 0, R.string.unhide);
                    menu.add(ContextMenu.NONE, 1, 0, R.string.remove);
                    menu.add(ContextMenu.NONE, 2, 0, R.string.show_url);
                }
            }
        });

        /* Filling in the list in onResume(). */
        refresher = new SwipeRefreshLayout(this);
        refresher.setOnRefreshListener(this);
        refresher.addView(list);

        LinearLayout cont = new LinearLayout(this);
        cont.setOrientation(LinearLayout.VERTICAL);
        cont.addView(refresher, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1));

        setContentView(cont);
    }

    private void refreshSeed(boolean force) {
        long seedAge = System.currentTimeMillis() - pref.getLong("last_menu_seed_ts", 0);
        if (force || seedAge < 0 || seedAge > Db.SEED_FETCH_INTERVAL) {
            Log.d("ChooserActivity", "seedAge " + seedAge);

            final Thread loader;

            seedRefreshMenu = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what == 1) {
                        Editor p = pref.edit();
                        p.putLong("last_menu_seed_ts", System.currentTimeMillis());
                        p.commit();

                        lista = new ScheduleAdapter(db.getScheduleList());
                        list.setAdapter(lista);

                        setProgressBarIndeterminateVisibility(false);
                        setProgressBarVisibility(false);
                    }
                }
            };

            loader = new Thread() {
                @Override
                public void run() {
                    db.refreshScheduleList();
                    if (seedRefreshMenu != null)
                        seedRefreshMenu.sendEmptyMessage(1);
                    else
                        Log.d("Chooser", "I had a handler but you eated it");
                }
            };

            setProgressBarIndeterminateVisibility(true);
            setProgressBarVisibility(true);

            loader.start();
        }
    }

    @Override
    public void onRefresh() {
        refresher.setRefreshing(true);
        refreshSeed(true);
        refresher.setRefreshing(false);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
        Giggity app = (Giggity) getApplication();
        DbSchedule sched = (DbSchedule) lista.getItem((int) mi.id);
        if (sched == null) {
        } else if (item.getItemId() == 0) {
            /* Refresh. */
            app.flushSchedule(sched.getUrl());
            openSchedule(sched, true);
        } else if (item.getItemId() == 3) {
            /* Unhide. */
            sched.flushHidden();
            /* Refresh. */
            app.flushSchedule(sched.getUrl());
            openSchedule(sched, false);
        } else if (item.getItemId() == 1) {
            /* Delete. */
            db.removeSchedule(sched.getUrl());
            onResume();
        } else {
            /* Show URL; try a QR code but fall back to a dialog if the app is not installed. */
            try {
                Intent intent = new Intent(BARCODE_ENCODE);
                intent.putExtra("ENCODE_TYPE", "TEXT_TYPE");
                intent.putExtra("ENCODE_DATA", sched.getUrl());
                startActivity(intent);
            } catch (ActivityNotFoundException e) {
                new AlertDialog.Builder(ChooserActivity.this).setTitle(sched.getTitle()).setMessage(sched.getUrl())
                        .show();
            }
        }
        return false;
    }

    @Override
    public void onResume() {
        /* Do this part in onResume so we automatically re-sort the list (and
         * pick up new items) when returning to the chooser. */
        super.onResume();

        lista = new ScheduleAdapter(db.getScheduleList());
        list.setAdapter(lista);

        /* For some reason Honeycomb+ show the progress indicator by default if the feature is enabled? */
        setProgressBarIndeterminateVisibility(false);
        setProgressBarVisibility(false);
    }

    @Override
    public void onPause() {
        super.onPause();
        seedRefreshMenu = null;
    }

    private void openSchedule(String url, boolean prefOnline, Schedule.Selections sel) {
        if (!url.contains("://"))
            url = "http://" + url;
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url), this, ScheduleViewActivity.class);
        intent.putExtra("PREFER_CACHED", !prefOnline);
        if (sel != null)
            intent.putExtra("SELECTIONS", sel);
        startActivity(intent);
    }

    private void openSchedule(DbSchedule event, boolean prefOnline) {
        if (!prefOnline) {
            if (pref.getBoolean("always_refresh", false)
                    || new Date().getTime() - event.getRtime().getTime() > 86400000)
                prefOnline = true;
        }
        openSchedule(event.getUrl(), prefOnline, null);
    }

    /* Process barcode scan results. This can be a few things:
        
       * Plain URL, in which case just handle it
       * zlib-compressed binary blob containing selection data exported by another Giggity
       * (gzip-compressed) JSON blob containing a menu.json entry
        
       We'll just have to figure out which one of the 3/4..
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == 0) {
            if (resultCode == RESULT_OK) {
                String url = intent.getStringExtra("SCAN_RESULT");
                byte[] bin = intent.getByteArrayExtra("SCAN_RESULT_BYTE_SEGMENTS_0");
                if (intent.hasExtra("SCAN_RESULT_BYTE_SEGMENTS_1")) {
                    Toast.makeText(this, "Your QR generator seems to have used multiple segments, "
                            + "this corrupts binary data!", Toast.LENGTH_LONG).show();
                }

                /* Start with #3, (gzipped) json blob */
                if (db.refreshSingleSchedule(bin)) {
                    return;
                }

                /* Or 2? */
                Schedule.Selections sel;
                try {
                    sel = new Schedule.Selections(bin);
                    url = sel.url;
                } catch (DataFormatException e) {
                    bin = null;
                    sel = null;
                }

                /* Nope, just a plain URL then hopefully.. Or something corrupted that will generate
                   a spectacular error message. \o/ */
                openSchedule(url, false, sel);
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        menu.add(Menu.NONE, 1, 5, R.string.settings).setShortcut('0', 's')
                .setIcon(R.drawable.ic_settings_white_24dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        menu.add(Menu.NONE, 2, 7, R.string.add_dialog).setShortcut('0', 'a').setIcon(R.drawable.ic_add_white_24dp)
                .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case 1:
            Intent intent = new Intent(this, SettingsActivity.class);
            startActivity(intent);
            return true;
        case 2:
            showAddDialog();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void showAddDialog() {
        AlertDialog.Builder d = new AlertDialog.Builder(this);
        d.setTitle(R.string.add_dialog);

        final EditText urlBox = new EditText(this);
        urlBox.setHint(R.string.enter_url);
        urlBox.setSingleLine();
        urlBox.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
        d.setView(urlBox);

        d.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                openSchedule(urlBox.getText().toString(), false, null);
            }
        });
        /* Apparently the "Go"/"Done" button still just simulates an ENTER keypress. Neat!...
           http://stackoverflow.com/questions/5677563/listener-for-done-button-on-edittext */
        urlBox.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
                    openSchedule(urlBox.getText().toString(), false, null);
                    return true;
                } else {
                    return false;
                }
            }
        });

        d.setNeutralButton(R.string.qr_scan, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                try {
                    Intent intent = new Intent(BARCODE_SCANNER);
                    intent.putExtra("SCAN_MODE", "QR_CODE_MODE");
                    startActivityForResult(intent, 0);
                } catch (ActivityNotFoundException e) {
                    new AlertDialog.Builder(ChooserActivity.this)
                            .setMessage("Please install the Barcode Scanner app").setTitle("Error").show();
                }
            }
        });
        d.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });

        d.show();
    }

    private class ScheduleAdapter extends BaseAdapter {
        ArrayList<Element> list;

        public ScheduleAdapter(AbstractList<DbSchedule> scheds) {
            ArrayList<Element> now, later, past;
            now = new ArrayList<>();
            later = new ArrayList<>();
            past = new ArrayList<>();
            for (DbSchedule sched : scheds) {
                if (sched.getStart().after(new Date()))
                    later.add(new Element(sched));
                else if (sched.getEnd().before(new Date()))
                    past.add(new Element(sched));
                else
                    now.add(new Element(sched));
            }

            list = new ArrayList<>();
            if (now.size() > 0) {
                list.add(new Element(R.string.chooser_now));
                list.addAll(now);
            }
            if (later.size() > 0) {
                list.add(new Element(R.string.chooser_later));
                list.addAll(later);
            }
            if (past.size() > 0) {
                list.add(new Element(R.string.chooser_past));
                list.addAll(past);
            }
        }

        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public Object getItem(int position) {
            return list.get(position).item;
        }

        @Override
        public long getItemId(int position) {
            return (long) position;
        }

        @Override
        public boolean isEnabled(int position) {
            return list.get(position).item != null;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return list.get(position).getView();
        }

        private class Element {
            String header;
            DbSchedule item;

            public Element(int res) {
                header = ChooserActivity.this.getResources().getString(res);
            }

            public Element(DbSchedule item_) {
                item = item_;
            }

            public View getView() {
                Giggity app = (Giggity) getApplication();
                if (item != null) {
                    LinearLayout ret = new LinearLayout(ChooserActivity.this);
                    TextView title, when;

                    title = new TextView(ChooserActivity.this);
                    title.setText(item.getTitle());
                    title.setTextSize(22);
                    title.setTextColor(getResources().getColor(R.color.dark_text));
                    ret.addView(title);

                    when = new TextView(ChooserActivity.this);
                    when.setText(Giggity.dateRange(item.getStart(), item.getEnd()));
                    when.setTextSize(12);
                    ret.addView(when);

                    ret.setOrientation(LinearLayout.VERTICAL);
                    app.setPadding(ret, 0, 3, 0, 4);

                    return ret;
                } else {
                    TextView ret = new TextView(ChooserActivity.this);
                    ret.setText(header);
                    ret.setTextSize(18);
                    ret.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
                    ret.setTextColor(getResources().getColor(R.color.dark_text));
                    app.setPadding(ret, 0, 24, 0, 3);

                    return ret;
                }
            }
        }
    }
}