Java tutorial
/* * 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(); } } }