Java tutorial
/* * Copyright (C) 2014 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.felkertech.n.tv.fragments; import android.app.Activity; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.graphics.drawable.Drawable; import android.media.tv.TvContract; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.app.BrowseFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ImageCardView; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.DisplayMetrics; import android.util.Log; import android.widget.Toast; import com.felkertech.channelsurfer.sync.SyncUtils; import com.felkertech.cumulustv.plugins.CumulusChannel; import com.felkertech.n.ActivityUtils; import com.felkertech.n.cumulustv.utils.DriveSettingsManager; import com.felkertech.n.cumulustv.R; import com.felkertech.n.cumulustv.model.ChannelDatabase; import com.felkertech.n.cumulustv.model.JsonChannel; import com.felkertech.n.cumulustv.model.Option; import com.felkertech.n.cumulustv.model.SuggestedChannels; import com.felkertech.n.cumulustv.receivers.GoogleDriveBroadcastReceiver; import com.felkertech.n.tv.Utils; import com.felkertech.n.tv.activities.DetailsActivity; import com.felkertech.n.tv.presenters.CardPresenter; import com.felkertech.n.tv.presenters.OptionsCardPresenter; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.drive.Drive; import com.google.android.gms.drive.DriveApi; import com.google.android.gms.drive.MetadataChangeSet; import org.json.JSONException; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; public class LeanbackFragment extends BrowseFragment implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private static final String TAG = LeanbackFragment.class.getSimpleName(); private static final boolean DEBUG = false; private static final int BACKGROUND_UPDATE_DELAY = 300; private static final int GRID_ITEM_WIDTH = 200; private static final int GRID_ITEM_HEIGHT = 200; public static final int REQUEST_CODE_CREATOR = 102; private static final int ORDER_GENRE_CATEGORY = 0; private static final int ORDER_ALL_CHANNELS = 1; private static final int ORDER_SUGGESTED_CHANNELS = 2; private static final int ORDER_GOOGLE_DRIVE = 3; private static final int ORDER_MANAGE_SETTINGS = 4; private final Handler mHandler = new Handler(); private ArrayObjectAdapter mRowsAdapter; private Drawable mDefaultBackground; private DisplayMetrics mMetrics; private Timer mBackgroundTimer; private BackgroundManager mBackgroundManager; private DriveSettingsManager sm; public GoogleApiClient gapi; public Activity mActivity; private GoogleDriveBroadcastReceiver broadcastReceiver = new GoogleDriveBroadcastReceiver() { @Override public void onDownloadCompleted() { refreshUI(); } @Override public void onUploadCompleted() { } @Override public void onNetworkActionCompleted() { if (DEBUG) { Log.d(TAG, "Some network action occurred"); } } }; @Override public void onActivityCreated(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); super.onActivityCreated(savedInstanceState); sm = new DriveSettingsManager(getActivity()); ActivityUtils.GoogleDrive.autoConnect(getActivity()); refreshUI(); } @Override public void onDestroy() { super.onDestroy(); if (null != mBackgroundTimer) { Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString()); mBackgroundTimer.cancel(); } LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(broadcastReceiver); } @Override public void onStart() { super.onStart(); LocalBroadcastManager.getInstance(getActivity()).registerReceiver(broadcastReceiver, new IntentFilter(GoogleDriveBroadcastReceiver.ACTION_STATUS_CHANGED)); } public void refreshUI() { prepareBackgroundManager(); setupUIElements(); loadRows(); setupEventListeners(); } private void loadRows() { // Here are my rows mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); if (mActivity == null) { mActivity = getActivity(); } // My channels if (mActivity == null && Build.VERSION.SDK_INT >= 23) { Toast.makeText(getContext(), R.string.toast_error_no_activity, Toast.LENGTH_SHORT).show(); return; } else if (mActivity == null) { return; } ChannelDatabase cd = ChannelDatabase.getInstance(mActivity); Map<String, ListRow> genresListRows = new HashMap<>(); try { CardPresenter channelCardPresenter = new CardPresenter(); ArrayObjectAdapter channelRowAdapter = new ArrayObjectAdapter(channelCardPresenter); int index = 0; for (JsonChannel jsonChannel : cd.getJsonChannels()) { if (DEBUG) { Log.d(TAG, "Got channels " + jsonChannel.getName()); Log.d(TAG, jsonChannel.getLogo()); } String[] genres = jsonChannel.getGenres(); for (String genre : genres) { if (!genresListRows.containsKey(genre)) { CardPresenter genresCardPresenter = new CardPresenter(); ArrayObjectAdapter genresObjectAdapter = new ArrayObjectAdapter(genresCardPresenter); HeaderItem genresHeader = new HeaderItem(ORDER_GENRE_CATEGORY, Utils.normalizeGenre(genre)); ListRow row = new ListRow(genresHeader, genresObjectAdapter); mRowsAdapter.add(row); genresListRows.put(genre, row); } ((ArrayObjectAdapter) genresListRows.get(genre).getAdapter()).add(jsonChannel); } channelRowAdapter.add(jsonChannel); index++; } HeaderItem header = new HeaderItem(ORDER_ALL_CHANNELS, getString(R.string.my_channels)); mRowsAdapter.add(new ListRow(header, channelRowAdapter)); } catch (JSONException e) { e.printStackTrace(); } // Second row is suggested channels CardPresenter suggestedChannelPresenter = new CardPresenter(); ArrayObjectAdapter suggestedChannelAdapter = new ArrayObjectAdapter(suggestedChannelPresenter); HeaderItem suggestedChannelsHeader = new HeaderItem(ORDER_SUGGESTED_CHANNELS, getString(R.string.suggested_channels)); CumulusChannel[] suggestedChannels = SuggestedChannels.getSuggestedChannels(); for (CumulusChannel jsonChannel : suggestedChannels) { suggestedChannelAdapter.add(jsonChannel); } mRowsAdapter.add(new ListRow(suggestedChannelsHeader, suggestedChannelAdapter)); // Third row is Drive HeaderItem driveHeader = new HeaderItem(ORDER_GOOGLE_DRIVE, getString(R.string.google_drive_sync)); OptionsCardPresenter drivePresenter = new OptionsCardPresenter(); ArrayObjectAdapter driveAdapter = new ArrayObjectAdapter(drivePresenter); driveAdapter.add(new Option(getResources().getDrawable(R.drawable.ic_google_drive), getString(R.string.connect_drive))); driveAdapter.add(new Option(getResources().getDrawable(R.drawable.ic_cloud_download), getString(R.string.settings_refresh_cloud_local))); driveAdapter.add(new Option(getResources().getDrawable(R.drawable.ic_google_drive_folder), getString(R.string.settings_switch_google_drive))); mRowsAdapter.add(new ListRow(driveHeader, driveAdapter)); // Fourth row is actions HeaderItem gridHeader = new HeaderItem(ORDER_MANAGE_SETTINGS, getString(R.string.manage)); OptionsCardPresenter mGridPresenter = new OptionsCardPresenter(); ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter); gridRowAdapter.add(new Option(getResources().getDrawable(R.drawable.ic_television), getString(R.string.manage_livechannels))); gridRowAdapter.add( new Option(getResources().getDrawable(R.drawable.ic_airplay), getString(R.string.manage_add_new))); mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter)); // Settings will become its own activity HeaderItem gridHeader2 = new HeaderItem(ORDER_MANAGE_SETTINGS, getString(R.string.settings)); OptionsCardPresenter mGridPresenter2 = new OptionsCardPresenter(); ArrayObjectAdapter gridRowAdapter2 = new ArrayObjectAdapter(mGridPresenter2); /* FIXME Plugin browser hidden due to #165 gridRowAdapter2.add(new Option(getResources().getDrawable(R.drawable.ic_animation), getString(R.string.settings_browse_plugins))); */ gridRowAdapter2.add(new Option(getResources().getDrawable(R.drawable.ic_book_open), getString(R.string.settings_view_licenses))); gridRowAdapter2.add(new Option(getResources().getDrawable(R.drawable.ic_delete_forever), getString(R.string.settings_reset_channel_data))); gridRowAdapter2.add(new Option(getResources().getDrawable(R.drawable.ic_help_circle_fill), getString(R.string.about_app))); mRowsAdapter.add(new ListRow(gridHeader2, gridRowAdapter2)); setAdapter(mRowsAdapter); } private void prepareBackgroundManager() { try { mBackgroundManager = BackgroundManager.getInstance(getActivity()); mBackgroundManager.attach(getActivity().getWindow()); mDefaultBackground = getResources().getDrawable(R.drawable.c_background5); mBackgroundManager.setDrawable(getResources().getDrawable(R.drawable.c_background5)); mMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); } catch (Exception ignored) { } } private void setupUIElements() { try { setBadgeDrawable(getActivity().getResources().getDrawable(R.mipmap.ic_launcher)); setTitle(getString(R.string.app_name)); // Badge, when set, takes precedent // over title setHeadersState(HEADERS_ENABLED); setHeadersTransitionOnBackEnabled(true); // set fastLane (or headers) background color setBrandColor(getResources().getColor(R.color.colorPrimary)); // set search icon color // setSearchAffordanceColor(getResources().getColor(R.color.search_opaque)); } catch (Exception ignored) { } } private void setupEventListeners() { /*setOnSearchClickedListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG) .show(); } });*/ setOnItemViewClickedListener(new ItemViewClickedListener()); setOnItemViewSelectedListener(new ItemViewSelectedListener()); } @Override public void onConnected(Bundle bundle) { Log.d(TAG, "onConnected"); sm.setGoogleDriveSyncable(gapi, new DriveSettingsManager.GoogleDriveListener() { @Override public void onActionFinished(boolean cloudToLocal) { Log.d(TAG, "Sync req after drive action"); final String info = TvContract.buildInputId(ActivityUtils.TV_INPUT_SERVICE); SyncUtils.requestSync(mActivity, info); if (cloudToLocal) { Toast.makeText(getActivity(), R.string.download_complete, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getActivity(), R.string.upload_complete, Toast.LENGTH_SHORT).show(); } } }); //Enable GDrive Log.d(TAG, sm.getString(R.string.sm_google_drive_id) + "<< for onConnected"); if (sm.getString(R.string.sm_google_drive_id).isEmpty()) { //We need a new file ActivityUtils.createDriveData(mActivity, gapi, driveContentsCallback); } else { //Great, user already has sync enabled, let's resync ActivityUtils.readDriveData(mActivity, gapi); Handler h = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); refreshUI(); } }; h.sendEmptyMessageDelayed(0, 4000); } } ResultCallback<DriveApi.DriveContentsResult> driveContentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() { @Override public void onResult(DriveApi.DriveContentsResult result) { MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder() .setTitle("cumulustv_channels.json") .setDescription( "JSON list of channels that can be imported using CumulusTV to view live streams") .setMimeType("application/json").build(); IntentSender intentSender = Drive.DriveApi.newCreateFileActivityBuilder() .setActivityTitle("cumulustv_channels.json").setInitialMetadata(metadataChangeSet) .setInitialDriveContents(result.getDriveContents()).build(ActivityUtils.GoogleDrive.gapi); try { mActivity.startIntentSenderForResult(intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.w(TAG, "Unable to send intent", e); } } }; @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { if (DEBUG) { Log.d(TAG, "Error connecting " + connectionResult.getErrorCode()); Log.d(TAG, "oCF " + connectionResult.toString()); } if (connectionResult.hasResolution()) { try { connectionResult.startResolutionForResult(mActivity, ActivityUtils.RESOLVE_CONNECTION_REQUEST_CODE); } catch (IntentSender.SendIntentException e) { // Unable to resolve, message user appropriately } } else { GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), mActivity, 0).show(); } } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof JsonChannel) { JsonChannel jsonChannel = (JsonChannel) item; Log.d(TAG, "Item: " + item.toString()); Intent intent = new Intent(mActivity, DetailsActivity.class); intent.putExtra(VideoDetailsFragment.EXTRA_JSON_CHANNEL, jsonChannel.toString()); Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(mActivity, ((ImageCardView) itemViewHolder.view).getMainImageView(), DetailsActivity.SHARED_ELEMENT_NAME).toBundle(); mActivity.startActivity(intent, bundle); } else if (item instanceof Option) { String title = ((Option) item).getText(); if (title.equals(getString(R.string.manage_livechannels))) { ActivityUtils.launchLiveChannels(mActivity); } else if (title.equals(getString(R.string.manage_add_suggested))) { ActivityUtils.openSuggestedChannels(mActivity, gapi); } else if (title.equals(getString(R.string.manage_add_new))) { ActivityUtils.openPluginPicker(true, mActivity); } else if (title.equals(getString(R.string.connect_drive))) { ActivityUtils.GoogleDrive.connect(mActivity); } else if (title.equals(getString(R.string.settings_switch_google_drive))) { ActivityUtils.GoogleDrive.pickDriveFile(mActivity); } else if (title.equals(getString(R.string.settings_browse_plugins))) { ActivityUtils.browsePlugins(mActivity); } else if (title.equals(getString(R.string.settings_switch_google_drive))) { ActivityUtils.switchGoogleDrive(mActivity, gapi); } else if (title.equals(getString(R.string.settings_refresh_cloud_local))) { ActivityUtils.readDriveData(mActivity, gapi); Handler h = new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); refreshUI(); } }; h.sendEmptyMessageDelayed(0, 4000); } else if (title.equals(getString(R.string.settings_view_licenses))) { ActivityUtils.oslClick(mActivity); } else if (title.equals(getString(R.string.settings_reset_channel_data))) { ActivityUtils.deleteChannelData(mActivity, gapi); } else if (title.equals(getString(R.string.about_app))) { ActivityUtils.openAbout(mActivity); } else { Toast.makeText(mActivity, ((String) item), Toast.LENGTH_SHORT).show(); } } } } private final class ItemViewSelectedListener implements OnItemViewSelectedListener { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof JsonChannel) { startBackgroundTimer(); } } } private void startBackgroundTimer() { if (null != mBackgroundTimer) { mBackgroundTimer.cancel(); } mBackgroundTimer = new Timer(); mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY); } private class UpdateBackgroundTask extends TimerTask { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { } }); } } }