com.arantius.tivocommander.SeasonPass.java Source code

Java tutorial

Introduction

Here is the source code for com.arantius.tivocommander.SeasonPass.java

Source

/*
DVR Commander for TiVo allows control of a TiVo Premiere device.
Copyright (C) 2011  Anthony Lieuallen (arantius@gmail.com)
    
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
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 com.arantius.tivocommander;

import java.util.ArrayList;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Activity;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.arantius.tivocommander.rpc.MindRpc;
import com.arantius.tivocommander.rpc.request.SubscriptionSearch;
import com.arantius.tivocommander.rpc.request.SubscriptionsReprioritize;
import com.arantius.tivocommander.rpc.request.Unsubscribe;
import com.arantius.tivocommander.rpc.response.MindRpcResponse;
import com.arantius.tivocommander.rpc.response.MindRpcResponseListener;
import com.fasterxml.jackson.databind.JsonNode;
import com.mobeta.android.dslv.DragSortListView;

// TODO: This copies a lot from ShowList; be DRY?

public class SeasonPass extends ListActivity implements OnItemLongClickListener, OnClickListener {
    protected class SubscriptionAdapter extends ArrayAdapter<JsonNode> {
        protected ColorDrawable mBlankLogoDrawable;

        public SubscriptionAdapter() {
            super(SeasonPass.this, 0, mSubscriptionData);

            mBlankLogoDrawable = new ColorDrawable(0x00000000);
            Rect r = new Rect(0, 0, 65, 55); // Logo images are 65x55 px.
            mBlankLogoDrawable.setBounds(r);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;

            if (mSubscriptionStatus.get(position) == SubscriptionStatus.MISSING) {
                // If the data for this position is missing, fetch it and more, if they
                // exist, up to a limit of MAX_REQUEST_BATCH.
                ArrayList<String> subscriptionIds = new ArrayList<String>();
                ArrayList<Integer> slots = new ArrayList<Integer>();
                int i = position;
                while (i < mSubscriptionData.size()) {
                    if (mSubscriptionStatus.get(i) == SubscriptionStatus.MISSING) {
                        String subscriptionId = mSubscriptionIds.get(i);
                        subscriptionIds.add(subscriptionId);
                        slots.add(i);
                        mSubscriptionStatus.set(i, SubscriptionStatus.LOADING);
                        if (subscriptionIds.size() >= MAX_REQUEST_BATCH) {
                            break;
                        }
                    }
                    i++;
                }

                SubscriptionSearch req = new SubscriptionSearch(subscriptionIds);
                mRequestSlotMap.put(req.getRpcId(), slots);
                MindRpc.addRequest(req, mDetailCallback);
            }

            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (mSubscriptionStatus.get(position) == SubscriptionStatus.LOADED) {
                // If this item is available, display it.
                v = vi.inflate(R.layout.item_season_pass, parent, false);
                v.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));

                final JsonNode item = mSubscriptionData.get(position);

                ((TextView) v.findViewById(R.id.index)).setText(Integer.toString(position + 1));

                ((TextView) v.findViewById(R.id.show_title))
                        .setText(Utils.stripQuotes(item.path("title").asText()));

                ((ImageView) v.findViewById(R.id.icon_until_deleted)).setVisibility(
                        "forever".equals(item.path("keepBehavior").asText()) ? View.VISIBLE : View.GONE);
                String keepNum = item.path("maxRecordings").asText();
                if ("0".equals(keepNum)) {
                    keepNum = "all";
                }
                ((TextView) v.findViewById(R.id.keep_num)).setText(keepNum);

                final boolean newOnly = "firstRunOnly".equals(item.path("showStatus").asText());
                final int newOnlyVis = newOnly ? View.VISIBLE : View.GONE;
                ((TextView) v.findViewById(R.id.new_comma)).setVisibility(newOnlyVis);
                ((ImageView) v.findViewById(R.id.badge_new)).setVisibility(newOnlyVis);
                ((TextView) v.findViewById(R.id.new_only)).setVisibility(newOnlyVis);

                TextView channelView = (TextView) v.findViewById(R.id.show_channel);
                ImageView dragHandle = (ImageView) v.findViewById(R.id.drag_handle);
                if (mInReorderMode) {
                    dragHandle.setVisibility(View.VISIBLE);
                    channelView.setVisibility(View.GONE);
                } else {
                    dragHandle.setVisibility(View.GONE);
                    channelView.setVisibility(View.VISIBLE);

                    final JsonNode channel = item.path("idSetSource").path("channel");
                    channelView.setText(channel.path("channelNumber").asText());
                    channelView.setCompoundDrawables(null, null, null, mBlankLogoDrawable);
                    if (channel.has("logoIndex")) {
                        // Wish lists don't have channels, so get the image conditionally.
                        final String channelLogoUrl = "http://" + MindRpc.mTivoDevice.addr + "/ChannelLogo/icon-"
                                + channel.path("logoIndex") + "-1.png";
                        new DownloadImageTask(SeasonPass.this, channelView).execute(channelLogoUrl);
                    }
                }
            } else {
                // Otherwise give a loading indicator.
                v = vi.inflate(R.layout.progress, parent, false);
            }

            return v;
        }

    }

    protected enum SubscriptionStatus {
        LOADED, LOADING, MISSING;
    }

    protected final static int MAX_REQUEST_BATCH = 5;
    protected boolean mInReorderMode = false;
    protected SubscriptionAdapter mListAdapter;
    protected int mLongClickPosition;
    protected final SparseArray<ArrayList<Integer>> mRequestSlotMap = new SparseArray<ArrayList<Integer>>();
    protected final ArrayList<JsonNode> mSubscriptionData = new ArrayList<JsonNode>();
    protected final ArrayList<String> mSubscriptionIds = new ArrayList<String>();
    protected ArrayList<String> mSubscriptionIdsBeforeReorder = new ArrayList<String>();
    protected final ArrayList<SubscriptionStatus> mSubscriptionStatus = new ArrayList<SubscriptionStatus>();

    protected MindRpcResponseListener mDetailCallback = new MindRpcResponseListener() {
        public void onResponse(MindRpcResponse response) {
            final JsonNode items = response.getBody().path("subscription");

            ArrayList<Integer> slotMap = mRequestSlotMap.get(response.getRpcId());

            for (int i = 0; i < items.size(); i++) {
                int pos = slotMap.get(i);
                JsonNode item = items.get(i);
                mSubscriptionData.set(pos, item);
                mSubscriptionStatus.set(pos, SubscriptionStatus.LOADED);
            }

            mRequestSlotMap.remove(response.getRpcId());
            mListAdapter.notifyDataSetChanged();
        }
    };

    protected final OnItemClickListener mOnClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final JsonNode item = mSubscriptionData.get(position);
            if (item == null) {
                return;
            }

            final String collectionId = item.path("idSetSource").path("collectionId").asText();
            if ("".equals(collectionId)) {
                return;
            }

            Intent intent = new Intent(SeasonPass.this, ExploreTabs.class);
            intent.putExtra("collectionId", collectionId);
            startActivityForResult(intent, 1);
        }
    };

    final protected DragSortListView.DropListener mOnDrop = new DragSortListView.DropListener() {
        public void drop(int from, int to) {
            JsonNode item = mListAdapter.getItem(from);
            mListAdapter.remove(item);
            mListAdapter.insert(item, to);
            String id = mSubscriptionIds.get(from);
            mSubscriptionIds.remove(id);
            mSubscriptionIds.add(to, id);
        }
    };

    public void onClick(DialogInterface dialog, int which) {
        JsonNode sub = mSubscriptionData.get(mLongClickPosition);

        // TODO: De-dupe vs. Explore.doRecord().
        switch (which) {
        case 0:
            Intent intent = new Intent(getBaseContext(), SubscribeCollection.class);
            intent.putExtra("collectionId", sub.path("idSetSource").path("collectionId").asText());
            intent.putExtra("subscriptionId", sub.path("subscriptionId").asText());
            intent.putExtra("subscriptionJson", Utils.stringifyToJson(sub));
            startActivity(intent);
            break;
        case 1:
            setProgressBarIndeterminateVisibility(true);
            MindRpc.addRequest(new Unsubscribe(sub.path("subscriptionId").asText()), new MindRpcResponseListener() {
                public void onResponse(MindRpcResponse response) {
                    setProgressBarIndeterminateVisibility(false);
                    mSubscriptionData.remove(mLongClickPosition);
                    mSubscriptionIds.remove(mLongClickPosition);
                    mSubscriptionStatus.remove(mLongClickPosition);
                    mListAdapter.notifyDataSetChanged();
                }
            });
            break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != Activity.RESULT_OK) {
            return;
        }

        if (data.getBooleanExtra("refresh", false)) {
            startRequest();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (MindRpc.init(this, null)) {
            return;
        }

        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        Utils.activateHomeButton(this);
        setTitle("Season Pass Manager");
        setContentView(R.layout.list_season_pass);

        mListAdapter = new SubscriptionAdapter();
        setListAdapter(mListAdapter);
        DragSortListView dslv = (DragSortListView) getListView();
        dslv.setOnItemClickListener(mOnClickListener);
        dslv.setDropListener(mOnDrop);
        dslv.setLongClickable(true);
        dslv.setOnItemLongClickListener(this);

        startRequest();
    }

    protected void startRequest() {
        mSubscriptionData.clear();
        mSubscriptionIds.clear();
        mSubscriptionStatus.clear();
        MindRpcResponseListener idSequenceCallback = new MindRpcResponseListener() {
            public void onResponse(MindRpcResponse response) {
                JsonNode body = response.getBody();

                for (JsonNode node : body.path("objectIdAndType")) {
                    mSubscriptionIds.add(node.asText());
                }
                for (int i = 0; i < mSubscriptionIds.size(); i++) {
                    mSubscriptionData.add(null);
                    mSubscriptionStatus.add(SubscriptionStatus.MISSING);
                }
                mListAdapter.notifyDataSetChanged();
            }
        };
        MindRpc.addRequest(new SubscriptionSearch(), idSequenceCallback);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Utils.createFullOptionsMenu(menu, this);
        return true;
    }

    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        final JsonNode sub = mSubscriptionData.get(position);
        final String subType = sub.path("idSetSource").path("type").asText();
        if ("wishListSource".equals(subType)) {
            return false;
        }

        mLongClickPosition = position;

        final ArrayList<String> choices = new ArrayList<String>();
        choices.add(Explore.RecordActions.SP_MODIFY.toString());
        choices.add(Explore.RecordActions.SP_CANCEL.toString());
        final ArrayAdapter<String> choicesAdapter = new ArrayAdapter<String>(this,
                android.R.layout.select_dialog_item, choices);

        Builder dialogBuilder = new AlertDialog.Builder(this);
        dialogBuilder.setTitle("Operation?");
        dialogBuilder.setAdapter(choicesAdapter, this);
        AlertDialog dialog = dialogBuilder.create();
        dialog.show();

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return Utils.onOptionsItemSelected(item, this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Utils.log("Activity:Pause:SeasonPass");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Utils.log("Activity:Resume:SeasonPass");
        if (MindRpc.init(this, null)) {
            return;
        }
    }

    public void reorderApply(View unusedView) {
        Utils.log("SeasonPass::reorderApply() " + Boolean.toString(mInReorderMode));

        boolean noChange = true;
        ArrayList<String> subIds = new ArrayList<String>();
        for (int i = 0; i < mSubscriptionIds.size(); i++) {
            if (mSubscriptionIds.get(i) != mSubscriptionIdsBeforeReorder.get(i)) {
                noChange = false;
            }
            subIds.add(mSubscriptionData.get(i).path("subscriptionId").asText());
        }

        final ProgressDialog d = new ProgressDialog(this);
        final MindRpcResponseListener onReorderComplete = new MindRpcResponseListener() {
            public void onResponse(MindRpcResponse response) {
                if (d.isShowing()) {
                    d.dismiss();
                }

                // Flip the buttons.
                findViewById(R.id.reorder_enable).setVisibility(View.VISIBLE);
                findViewById(R.id.reorder_apply).setVisibility(View.GONE);
                // Turn off the drag handles.
                mInReorderMode = false;
                mListAdapter.notifyDataSetChanged();
            }
        };

        if (noChange) {
            // If there was no change, switch the UI back immediately.
            onReorderComplete.onResponse(null);
        } else {
            // Otherwise show a dialog while we do the RPC.
            d.setIndeterminate(true);
            d.setTitle("Saving ...");
            d.setMessage("Saving new season pass order.  " + "Patience please, this takes a while.");
            d.setCancelable(false);
            d.show();

            SubscriptionsReprioritize req = new SubscriptionsReprioritize(subIds);
            MindRpc.addRequest(req, onReorderComplete);
        }
    }

    public void reorderEnable(View unusedView) {
        Utils.log("SeasonPass::reorderEnable() " + Boolean.toString(mInReorderMode));

        final ArrayList<String> subscriptionIds = new ArrayList<String>();
        final ArrayList<Integer> slots = new ArrayList<Integer>();
        int i = 0;
        while (i < mSubscriptionData.size()) {
            if (mSubscriptionStatus.get(i) == SubscriptionStatus.MISSING) {
                String subscriptionId = mSubscriptionIds.get(i);
                subscriptionIds.add(subscriptionId);
                slots.add(i);
                mSubscriptionStatus.set(i, SubscriptionStatus.LOADING);
            }
            i++;
        }

        final ProgressDialog d = new ProgressDialog(this);
        final MindRpcResponseListener onAllPassesLoaded = new MindRpcResponseListener() {
            public void onResponse(MindRpcResponse response) {
                if (response != null) {
                    mDetailCallback.onResponse(response);
                }
                d.dismiss();

                // Save the state before ordering.
                mSubscriptionIdsBeforeReorder.clear();
                mSubscriptionIdsBeforeReorder.addAll(mSubscriptionIds);
                // Flip the buttons.
                findViewById(R.id.reorder_enable).setVisibility(View.GONE);
                findViewById(R.id.reorder_apply).setVisibility(View.VISIBLE);
                // Show the drag handles.
                mInReorderMode = true;
                mListAdapter.notifyDataSetChanged();
            }
        };

        if (subscriptionIds.size() == 0) {
            // No subscriptions need loading? Proceed immediately.
            onAllPassesLoaded.onResponse(null);
        } else {
            // Otherwise, show dialog and start loading.
            d.setIndeterminate(true);
            d.setTitle("Preparing ...");
            d.setMessage("Loading all season pass data.");
            d.setCancelable(false);
            d.show();

            final SubscriptionSearch req = new SubscriptionSearch(subscriptionIds);
            mRequestSlotMap.put(req.getRpcId(), slots);
            MindRpc.addRequest(req, onAllPassesLoaded);
        }
    }
}