com.google.android.media.tv.companionlibrary.ChannelSetupFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.media.tv.companionlibrary.ChannelSetupFragment.java

Source

/*
 * Copyright 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.media.tv.companionlibrary;

import android.animation.LayoutTransition;
import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.google.android.media.tv.companionlibrary.model.Channel;

import java.util.ArrayList;

/**
 * The ChannelSetupFragment class provides a simple extendable class to create a user interface
 * for scanning channels. This fragment will be displayed to the user when they are setting up
 * your app's channels for the first time in the setup activity.
 * </p>
 * There are a handful of methods which can be used to customize and theme the user interface.
 * Methods like {@link #setBackgroundColor(int)}, {@link #setTitle(CharSequence)}, and
 * {@link #setBadge(Drawable)} can be called when your fragment first is initialized to change their
 * values.
 * </p>
 * Additionally, your fragment can override certain methods to provide custom functionality to your
 * setup activity. When the user clicks on the button to start scanning for your channels, the
 * {@link #onScanStarted()} method will be called. Here your {@link EpgSyncJobService} should run
 * the {@link EpgSyncJobService#requestImmediateSync(Context, String, ComponentName)} and start
 * syncing.
 * </p>
 * When your service is done scanning, the method {@link #onScanFinished()} will called. Here should
 * be the code to exit the setup activity and return to the system TV app.
 * </p>
 * While channels are being scanned, the methods {@link #onChannelScanCompleted(int, int)} and
 * {@link #onScannedChannel(CharSequence, CharSequence)} will be called to provide status updates
 * during scans.
 * This information can be provided to the user by calling
 * {@link #setDescription(CharSequence)}. Additionally, a progress bar will automatically
 * increment.
 * </p>
 * Users should be able to manually start and stop the scanning process and the button text should
 * be updated by calling {@link #setButtonText(CharSequence)}. If
 * {@link #setChannelListVisibility(boolean)} is {@code true}, the channels will appear on the
 * screen as they are scanned.
 */
public abstract class ChannelSetupFragment extends Fragment {
    private static final String TAG = "ScanFragment";
    private static final boolean DEBUG = false;

    private ProgressBar mProgressBar;
    private View mChannelHolder;
    private TextView mScanningMessage;
    private ChannelAdapter mAdapter;
    private Button mCancelButton;
    private boolean mFinishedScan;
    private View mChannelScanLayout;
    private TextView mTitle;
    private ImageView mBadge;

    private final BroadcastReceiver mSyncStatusChangedReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, final Intent intent) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mFinishedScan) {
                        return;
                    }
                    String syncStatusChangedInputId = intent.getStringExtra(EpgSyncJobService.BUNDLE_KEY_INPUT_ID);
                    if (syncStatusChangedInputId != null && syncStatusChangedInputId.equals(getInputId())) {
                        String syncStatus = intent.getStringExtra(EpgSyncJobService.SYNC_STATUS);
                        if (syncStatus.equals(EpgSyncJobService.SYNC_STARTED)) {
                            if (DEBUG) {
                                Log.d(TAG, "Sync status: Started");
                            }
                        } else if (syncStatus.equals(EpgSyncJobService.SYNC_SCANNED)) {
                            int channelsScanned = intent.getIntExtra(EpgSyncJobService.BUNDLE_KEY_CHANNELS_SCANNED,
                                    0);
                            int channelCount = intent.getIntExtra(EpgSyncJobService.BUNDLE_KEY_CHANNEL_COUNT, 0);
                            updateScanProgress(++channelsScanned, channelCount);
                            String channelDisplayName = intent
                                    .getStringExtra(EpgSyncJobService.BUNDLE_KEY_SCANNED_CHANNEL_DISPLAY_NAME);
                            String channelDisplayNumber = intent
                                    .getStringExtra(EpgSyncJobService.BUNDLE_KEY_SCANNED_CHANNEL_DISPLAY_NUMBER);
                            if (DEBUG) {
                                Log.d(TAG, "Sync status: Channel Scanned");
                                Log.d(TAG, "Scanned " + channelsScanned + " out of " + channelCount);
                            }
                            onScannedChannel(channelDisplayName, channelDisplayNumber);
                            mAdapter.add(new Pair<>(channelDisplayName, channelDisplayNumber));
                        } else if (syncStatus.equals(EpgSyncJobService.SYNC_FINISHED)) {
                            if (DEBUG) {
                                Log.d(TAG, "Sync status: Finished");
                            }
                            finishScan(true);
                        } else if (syncStatus.equals(EpgSyncJobService.SYNC_ERROR)) {
                            int errorCode = intent.getIntExtra(EpgSyncJobService.BUNDLE_KEY_ERROR_REASON, 0);
                            if (DEBUG) {
                                Log.d(TAG, "Error occurred: " + errorCode);
                            }
                            onScanError(errorCode);
                        }
                    }
                }
            });
        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutResourceId(), container, false);
        // Make sure this view is focused
        view.requestFocus();
        mProgressBar = (ProgressBar) view.findViewById(R.id.tune_progress);
        mScanningMessage = (TextView) view.findViewById(R.id.tune_description);
        mTitle = (TextView) view.findViewById(R.id.tune_title);
        mBadge = (ImageView) view.findViewById(R.id.tune_icon);
        mChannelHolder = view.findViewById(R.id.channel_holder);
        mCancelButton = (Button) view.findViewById(R.id.tune_cancel);

        ListView channelList = (ListView) view.findViewById(R.id.channel_list);
        mAdapter = new ChannelAdapter();
        channelList.setAdapter(mAdapter);
        channelList.setOnItemClickListener(null);

        ViewGroup progressHolder = (ViewGroup) view.findViewById(R.id.progress_holder);
        LayoutTransition transition = new LayoutTransition();
        transition.enableTransitionType(LayoutTransition.CHANGING);
        progressHolder.setLayoutTransition(transition);

        mCancelButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finishScan(false);
            }
        });
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mSyncStatusChangedReceiver,
                new IntentFilter(EpgSyncJobService.ACTION_SYNC_STATUS_CHANGED));

        mChannelScanLayout = view;
        setChannelListVisibility(false);
        setBackgroundColor(getResources().getColor(android.R.color.holo_blue_dark));
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        onScanStarted();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mSyncStatusChangedReceiver);
    }

    /**
     * This method returns the layout for this fragment.
     *
     * @return Resource id for the fragment layout.
     */
    public int getLayoutResourceId() {
        return R.layout.tif_channel_setup;
    }

    /**
     * Sets the background color for the layout. This allows the setup fragment to be themed.
     *
     * @param backgroundColor The color for the background.
     */
    public void setBackgroundColor(@ColorInt int backgroundColor) {
        mChannelScanLayout.findViewById(R.id.channel_setup_layout).setBackgroundColor(backgroundColor);
    }

    private void updateScanProgress(int channelsScanned, int channelCount) {
        if (channelCount > 0) {
            mProgressBar.setMax(channelCount);
            mProgressBar.setProgress(channelsScanned);
        }
        onChannelScanCompleted(channelsScanned, channelCount);
    }

    /**
     * Finishes the current scan thread. This fragment will be popped after the scan thread ends.
     *
     * @param scanCompleted a flag which indicates the scan was completed successfully or canceled.
     */
    private void finishScan(boolean scanCompleted) {
        // Hides the cancel button.
        mFinishedScan = true;
        mCancelButton.setEnabled(false);
        onScanFinished();
    }

    /**
     * This method will be called when scanning should begin. Developers should request a new
     * immediate sync by calling
     * {@link EpgSyncJobService#requestImmediateSync(Context, String, ComponentName)}.
     */
    public abstract void onScanStarted();

    /**
     * @return The input id for your Tv service.
     */
    public abstract String getInputId();

    /**
     * This method will be called when scanning ends. Developers may want to notify the user that
     * scanning has completed and allow them to exit the activity.
     */
    public abstract void onScanFinished();

    /**
     * This method will be called when an error occurs in scanning. Developers may want to notify
     * the user that an error has happened or resolve the error.
     *
     * @param reason A constant indicating the type of error that has happened. Possible values are
     * {@link EpgSyncJobService#ERROR_EPG_SYNC_CANCELED},
     * {@link EpgSyncJobService#ERROR_INPUT_ID_NULL},
     * {@link EpgSyncJobService#ERROR_NO_PROGRAMS},
     * {@link EpgSyncJobService#ERROR_NO_CHANNELS}, or
     * {@link EpgSyncJobService#ERROR_DATABASE_INSERT},
     */
    public void onScanError(int reason) {
    }

    /**
     * Update the description that will be displayed underneath the progress bar. This could be used
     * to state the current progress of the scan.
     * @param message The message to be displayed.
     */
    public void setDescription(CharSequence message) {
        mScanningMessage.setText(message);
    }

    /**
     * Update the description that will be displayed underneath the progress bar. This could be used
     * to state the current progress of the scan.
     * @param resId The string resource to be displayed.
     */
    public void setDescription(int resId) {
        mScanningMessage.setText(resId);
    }

    /**
     * Sets the title that will be displayed above the progress bar. This could be used to display
     * your app's title.
     * @param title The title to be displayed.
     */
    public void setTitle(CharSequence title) {
        mTitle.setText(title);
    }

    /**
     * Sets the title that will be displayed above the progress bar. This could be used to display
     * your app's title.
     * @param resId The string resource to be displayed.
     */
    public void setTitle(int resId) {
        mTitle.setText(resId);
    }

    /**
     * Sets the image that will be displayed to the left of the progress bar. This could be used to
     * display your app's icon.
     * @param drawable The drawable to be displayed.
     */
    public void setBadge(Drawable drawable) {
        mBadge.setImageDrawable(drawable);
    }

    /**
     * Sets the image that will be displayed to the left of the progress bar. This could be used to
     * display your app's icon.
     * @param bitmap The bitmap image to be displayed.
     */
    public void setBadge(Bitmap bitmap) {
        mBadge.setImageBitmap(bitmap);
    }

    /**
     * Sets the text that will appear on the button on the screen.
     * @param message The button text.
     */
    public void setButtonText(CharSequence message) {
        mCancelButton.setText(message);
    }

    /**
     * Sets the text that will appear on the button on the screen.
     * @param resId The string resource to be displayed.
     */
    public void setButtonText(int resId) {
        mCancelButton.setText(resId);
    }

    /**
     * Sets whether the channel list will be displayed to the right of the screen, displaying
     * each channel as it is scanned.
     * @param visible If true, the list will be displayed. Otherwise it will be hidden.
     */
    public void setChannelListVisibility(boolean visible) {
        mChannelHolder.setVisibility(visible ? View.VISIBLE : View.GONE);
    }

    /**
     * This method will be called when a channel has been completely scanned. It can be overriden
     * to display custom information about this channel to the user.
     *
     * @param displayName {@link Channel#getDisplayName()} for the scanned channel.
     * @param displayNumber {@link Channel#getDisplayNumber()} ()} for the scanned channel.
     */
    public void onScannedChannel(CharSequence displayName, CharSequence displayNumber) {
        if (DEBUG) {
            Log.d(TAG, "Scanned channel data: " + displayName + ", " + displayNumber);
        }
    }

    /**
     * This method will be called when another channel has been scanned. It can be overriden to
     * display custom information about the current progress of the scan.
     *
     * @param channelsScanned The number of channels that have been scanned so far.
     * @param channelCount The total number of channels that need to be scanned.
     */
    public void onChannelScanCompleted(int channelsScanned, int channelCount) {
    }

    private class ChannelAdapter extends BaseAdapter {
        private final ArrayList<Pair<String, String>> mChannels;

        public ChannelAdapter() {
            mChannels = new ArrayList<>();
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public boolean isEnabled(int pos) {
            return false;
        }

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

        @Override
        public Object getItem(int pos) {
            return pos;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final Context context = parent.getContext();

            if (convertView == null) {
                LayoutInflater inflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.tif_channel_list, parent, false);
            }

            TextView channelName = (TextView) convertView.findViewById(R.id.channel_name);
            channelName.setText(mChannels.get(position).first);

            TextView channelNum = (TextView) convertView.findViewById(R.id.channel_num);
            channelNum.setText(mChannels.get(position).second);
            return convertView;
        }

        public void add(Pair<String, String> channelData) {
            mChannels.add(channelData);
            notifyDataSetChanged();
        }
    }
}