com.arantius.tivocommander.Explore.java Source code

Java tutorial

Introduction

Here is the source code for com.arantius.tivocommander.Explore.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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.arantius.tivocommander.rpc.MindRpc;
import com.arantius.tivocommander.rpc.request.RecordingSearch;
import com.arantius.tivocommander.rpc.request.RecordingUpdate;
import com.arantius.tivocommander.rpc.request.SubscriptionSearch;
import com.arantius.tivocommander.rpc.request.UiNavigate;
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;

public class Explore extends ExploreCommon {
    enum RecordActions {
        DONT_RECORD("Don't record"), RECORD("Record this episode"), RECORD_STOP(
                "Stop recording in progress"), SP_ADD(
                        "Add season pass"), SP_CANCEL("Cancel season pass"), SP_MODIFY("Modify season pass");

        private final String mText;

        private RecordActions(String text) {
            mText = text;
        }

        @Override
        public String toString() {
            return mText;
        }
    }

    private final MindRpcResponseListener mDeleteListener = new MindRpcResponseListener() {
        public void onResponse(MindRpcResponse response) {
            getParent().setProgressBarIndeterminateVisibility(false);
            if (!("success".equals(response.getRespType()))) {
                Utils.logError("Delete attempt failed!");
                Utils.toast(Explore.this, "Delete failed!.", Toast.LENGTH_SHORT);
                return;
            }
            setRefreshResult();
            finish();
        }
    };

    private final MindRpcResponseListener mRecordingListener = new MindRpcResponseListener() {
        public void onResponse(MindRpcResponse response) {
            mRecording = response.getBody().path("recording").path(0);
            mRecordingState = mRecording.path("state").asText();
            mSubscriptionType = Utils.subscriptionTypeForRecording(mRecording);
            finishRequest();
        }
    };

    private final MindRpcResponseListener mSubscriptionListener = new MindRpcResponseListener() {
        public void onResponse(MindRpcResponse response) {
            mSubscription = response.getBody().path("subscription").path(0);
            if (mSubscription.isMissingNode()) {
                mSubscription = null;
                mSubscriptionId = null;
            } else {
                mSubscriptionId = mSubscription.path("subscriptionId").asText();
                final String subType = mSubscription.path("idSetSource").path("type").asText();
                if ("seasonPassSource".equals(subType)) {
                    mSubscriptionType = SubscriptionType.SEASON_PASS;
                } else if ("wishListSource".equals(subType)) {
                    mSubscriptionType = SubscriptionType.WISHLIST;
                }
            }
            finishRequest();
        }
    };

    private final ArrayList<String> mChoices = new ArrayList<String>();
    private JsonNode mRecording = null;
    private String mRecordingState = null;
    private int mRequestCount = 0;
    private SubscriptionType mSubscriptionType = null;
    private JsonNode mSubscription = null;
    private String mSubscriptionId = null;

    public void doDelete(View v) {
        // FIXME: Fails when deleting the currently-playing show.
        getParent().setProgressBarIndeterminateVisibility(true);
        String newState = "deleted";
        if (v.getId() == R.id.explore_btn_undelete) {
            newState = "complete";
        }
        // (Un-)Delete the recording ...
        final RecordingUpdate req = new RecordingUpdate(mRecordingId, newState);
        MindRpc.addRequest(req, mDeleteListener);
    }

    public void doRecord(View v) {
        ArrayAdapter<String> choicesAdapter = new ArrayAdapter<String>(this, android.R.layout.select_dialog_item,
                mChoices);
        Builder dialogBuilder = new AlertDialog.Builder(this);
        dialogBuilder.setTitle("Operation?");
        dialogBuilder.setAdapter(choicesAdapter, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int position) {
                setRefreshResult();

                String label = mChoices.get(position);
                if (RecordActions.DONT_RECORD.toString().equals(label)) {
                    getParent().setProgressBarIndeterminateVisibility(true);
                    MindRpc.addRequest(new RecordingUpdate(mRecordingId, "cancelled"),
                            new MindRpcResponseListener() {
                                public void onResponse(MindRpcResponse response) {
                                    getParent().setProgressBarIndeterminateVisibility(false);
                                    ImageView iconSubType = (ImageView) findViewById(R.id.icon_sub_type);
                                    TextView textSubType = (TextView) findViewById(R.id.text_sub_type);
                                    iconSubType.setVisibility(View.GONE);
                                    textSubType.setVisibility(View.GONE);
                                }
                            });
                } else if (RecordActions.RECORD.toString().equals(label)) {
                    Intent intent = new Intent(getBaseContext(), SubscribeOffer.class);
                    intent.putExtra("contentId", mContentId);
                    intent.putExtra("offerId", mOfferId);
                    startActivity(intent);
                } else if (RecordActions.RECORD_STOP.toString().equals(label)) {
                    getParent().setProgressBarIndeterminateVisibility(true);
                    MindRpc.addRequest(new RecordingUpdate(mRecordingId, "complete"),
                            new MindRpcResponseListener() {
                                public void onResponse(MindRpcResponse response) {
                                    getParent().setProgressBarIndeterminateVisibility(false);
                                    mRecordingId = null;
                                }
                            });
                } else if (RecordActions.SP_ADD.toString().equals(label)) {
                    Intent intent = new Intent(getBaseContext(), SubscribeCollection.class);
                    intent.putExtra("collectionId", mCollectionId);
                    startActivity(intent);
                    // TODO: Start for result, get subscription ID.
                } else if (RecordActions.SP_CANCEL.toString().equals(label)) {
                    getParent().setProgressBarIndeterminateVisibility(true);
                    MindRpc.addRequest(new Unsubscribe(mSubscriptionId), new MindRpcResponseListener() {
                        public void onResponse(MindRpcResponse response) {
                            getParent().setProgressBarIndeterminateVisibility(false);
                            mSubscriptionId = null;
                        }
                    });
                } else if (RecordActions.SP_MODIFY.toString().equals(label)) {
                    Intent intent = new Intent(getBaseContext(), SubscribeCollection.class);
                    intent.putExtra("collectionId", mCollectionId);
                    intent.putExtra("subscriptionId", mSubscriptionId);
                    intent.putExtra("subscriptionJson", Utils.stringifyToJson(mSubscription));
                    startActivity(intent);
                }
            }
        });
        AlertDialog dialog = dialogBuilder.create();
        dialog.show();
    }

    public void doUpcoming(View v) {
        Intent intent = new Intent(getBaseContext(), Upcoming.class);
        intent.putExtra("collectionId", mCollectionId);
        startActivity(intent);
    }

    public void doWatch(View v) {
        MindRpc.addRequest(new UiNavigate(mRecordingId), null);
        Intent intent = new Intent(this, NowShowing.class);
        startActivity(intent);
    }

    protected void finishRequest() {
        if (--mRequestCount != 0) {
            return;
        }

        getParent().setProgressBarIndeterminateVisibility(false);

        if (mRecordingId == null) {
            for (JsonNode recording : mContent.path("recordingForContentId")) {
                String state = recording.path("state").asText();
                if ("inProgress".equals(state) || "complete".equals(state) || "scheduled".equals(state)) {
                    mRecordingId = recording.path("recordingId").asText();
                    mRecordingState = state;
                    break;
                }
            }
        }

        // Fill mChoices based on the data we now have.
        if ("scheduled".equals(mRecordingState)) {
            mChoices.add(RecordActions.DONT_RECORD.toString());
        } else if ("inProgress".equals(mRecordingState)) {
            mChoices.add(RecordActions.RECORD_STOP.toString());
        } else if (mOfferId != null) {
            mChoices.add(RecordActions.RECORD.toString());
        }
        if (mSubscriptionId != null) {
            mChoices.add(RecordActions.SP_MODIFY.toString());
            mChoices.add(RecordActions.SP_CANCEL.toString());
        } else if (mCollectionId != null && !"movie".equals(mContent.path("collectionType").asText())
                && !mContent.has("movieYear")) {
            mChoices.add(RecordActions.SP_ADD.toString());
        }

        setContentView(R.layout.explore);

        // Show only appropriate buttons.
        findViewById(R.id.explore_btn_watch).setVisibility(
                "complete".equals(mRecordingState) || "inprogress".equals(mRecordingState) ? View.VISIBLE
                        : View.GONE);
        hideViewIfNull(R.id.explore_btn_upcoming, mCollectionId);
        if (mChoices.size() == 0) {
            findViewById(R.id.explore_btn_record).setVisibility(View.GONE);
        }
        // Delete / undelete buttons visible only if appropriate.
        findViewById(R.id.explore_btn_delete)
                .setVisibility("complete".equals(mRecordingState) ? View.VISIBLE : View.GONE);
        findViewById(R.id.explore_btn_undelete)
                .setVisibility("deleted".equals(mRecordingState) ? View.VISIBLE : View.GONE);

        // Display titles.
        String title = mContent.path("title").asText();
        String subtitle = mContent.path("subtitle").asText();
        ((TextView) findViewById(R.id.content_title)).setText(title);
        TextView subtitleView = ((TextView) findViewById(R.id.content_subtitle));
        if ("".equals(subtitle)) {
            subtitleView.setVisibility(View.GONE);
        } else {
            subtitleView.setText(subtitle);
        }

        // Display (only the proper) badges.
        if (mRecording != null && mRecording.path("episodic").asBoolean()
                && !mRecording.path("repeat").asBoolean()) {
            findViewById(R.id.badge_new).setVisibility(View.VISIBLE);
        }
        if (mRecording != null && mRecording.path("hdtv").asBoolean()) {
            findViewById(R.id.badge_hd).setVisibility(View.VISIBLE);
        }
        ImageView iconSubType = (ImageView) findViewById(R.id.icon_sub_type);
        TextView textSubType = (TextView) findViewById(R.id.text_sub_type);
        // TODO: Downloading state?
        if ("complete".equals(mRecordingState)) {
            iconSubType.setVisibility(View.GONE);
            textSubType.setVisibility(View.GONE);
        } else if ("inProgress".equals(mRecordingState)) {
            iconSubType.setImageResource(R.drawable.recording_recording);
            textSubType.setText(R.string.sub_recording);
        } else if (mSubscriptionType != null) {
            switch (mSubscriptionType) {
            case SEASON_PASS:
                iconSubType.setImageResource(R.drawable.todo_seasonpass);
                textSubType.setText(R.string.sub_season_pass);
                break;
            case SINGLE_OFFER:
                iconSubType.setImageResource(R.drawable.todo_single_offer);
                textSubType.setText(R.string.sub_single_offer);
                break;
            case WISHLIST:
                iconSubType.setImageResource(R.drawable.todo_wishlist);
                textSubType.setText(R.string.sub_wishlist);
                break;
            default:
                iconSubType.setVisibility(View.GONE);
                textSubType.setVisibility(View.GONE);
            }
        } else {
            iconSubType.setVisibility(View.GONE);
            textSubType.setVisibility(View.GONE);
        }

        // Display channel and time.
        if (mRecording != null) {
            String channelStr = "";
            JsonNode channel = mRecording.path("channel");
            if (!channel.isMissingNode()) {
                channelStr = String.format("%s %s, ", channel.path("channelNumber").asText(),
                        channel.path("callSign").asText());
            }

            // Lots of shows seem to be a few seconds short, add padding so that
            // rounding down works as expected. Magic number.
            final int minutes = (30 + mRecording.path("duration").asInt()) / 60;

            String durationStr = minutes >= 60 ? String.format(Locale.US, "%d hr", minutes / 60)
                    : String.format(Locale.US, "%d min", minutes);
            if (isRecordingPartial()) {
                durationStr += " (partial)";
            }
            ((TextView) findViewById(R.id.content_chan_len)).setText(channelStr + durationStr);

            String airTime = new SimpleDateFormat("EEE MMM d, hh:mm a", Locale.US)
                    .format(Utils.parseDateTimeStr(mRecording.path("actualStartTime").asText()));
            ((TextView) findViewById(R.id.content_air_time)).setText("Air time: " + airTime);
        } else {
            ((TextView) findViewById(R.id.content_chan_len)).setVisibility(View.GONE);
            ((TextView) findViewById(R.id.content_air_time)).setVisibility(View.GONE);
        }

        // Construct and display details.
        ArrayList<String> detailParts = new ArrayList<String>();
        int season = mContent.path("seasonNumber").asInt();
        int epNum = mContent.path("episodeNum").path(0).asInt();
        if (season != 0 && epNum != 0) {
            detailParts.add(String.format("Sea %d Ep %d", season, epNum));
        }
        if (mContent.has("mpaaRating")) {
            detailParts.add(mContent.path("mpaaRating").asText().toUpperCase(Locale.US));
        } else if (mContent.has("tvRating")) {
            detailParts.add("TV-" + mContent.path("tvRating").asText().toUpperCase(Locale.US));
        }
        detailParts.add(mContent.path("category").path(0).path("label").asText());
        int year = mContent.path("originalAirYear").asInt();
        if (year != 0) {
            detailParts.add(Integer.toString(year));
        }

        // Filter empty strings.
        for (int i = detailParts.size() - 1; i >= 0; i--) {
            if ("".equals(detailParts.get(i)) || null == detailParts.get(i)) {
                detailParts.remove(i);
            }
        }
        // Then format the parts into one string.
        String detail1 = "(" + Utils.join(", ", detailParts) + ") ";
        if ("() ".equals(detail1)) {
            detail1 = "";
        }

        String detail2 = mContent.path("description").asText();
        TextView detailView = ((TextView) findViewById(R.id.content_details));
        if (detail2 == null) {
            detailView.setText(detail1);
        } else {
            Spannable details = new SpannableString(detail1 + detail2);
            details.setSpan(new ForegroundColorSpan(Color.WHITE), detail1.length(), details.length(), 0);
            detailView.setText(details);
        }

        // Add credits.
        ArrayList<String> credits = new ArrayList<String>();
        for (JsonNode credit : mContent.path("credit")) {
            String role = credit.path("role").asText();
            if ("actor".equals(role) || "host".equals(role) || "guestStar".equals(role)) {
                credits.add(credit.path("first").asText() + " " + credit.path("last").asText());
            }
        }
        TextView creditsView = (TextView) findViewById(R.id.content_credits);
        creditsView.setText(Utils.join(", ", credits));

        // Find and set the banner image if possible.
        ImageView imageView = (ImageView) findViewById(R.id.content_image);
        View progressView = findViewById(R.id.content_image_progress);
        String imageUrl = Utils.findImageUrl(mContent);
        new DownloadImageTask(this, imageView, progressView).execute(imageUrl);
    }

    private void hideViewIfNull(int viewId, Object condition) {
        if (condition != null)
            return;
        findViewById(viewId).setVisibility(View.GONE);
    }

    private Boolean isRecordingPartial() {
        final Date actualStart = Utils.parseDateTimeStr(mRecording.path("actualStartTime").asText());
        final Date scheduledStart = Utils.parseDateTimeStr(mRecording.path("scheduledStartTime").asText());
        final Date actualEnd = Utils.parseDateTimeStr(mRecording.path("actualEndTime").asText());
        final Date scheduledEnd = Utils.parseDateTimeStr(mRecording.path("scheduledEndTime").asText());
        return actualStart.getTime() - scheduledStart.getTime() >= 30000
                || scheduledEnd.getTime() - actualEnd.getTime() >= 30000;
    }

    @Override
    protected void onContent() {
        finishRequest();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Utils.log(String.format("Explore: " + "contentId:%s collectionId:%s offerId:%s recordingId:%s", mContentId,
                mCollectionId, mOfferId, mRecordingId));

        // The one from ExploreCommon.
        mRequestCount = 1;

        if (mCollectionId != null) {
            mRequestCount++;
            MindRpc.addRequest(new SubscriptionSearch(mCollectionId), mSubscriptionListener);
        }

        if (mRecordingId != null) {
            mRequestCount++;
            MindRpc.addRequest(new RecordingSearch(mRecordingId), mRecordingListener);
        }
    }

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

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