diff options
author | Youngsang Cho <youngsang@google.com> | 2016-05-11 01:20:46 +0000 |
---|---|---|
committer | Youngsang Cho <youngsang@google.com> | 2016-05-11 01:20:46 +0000 |
commit | 3a72b93e554bd22a5c64e71a6956d9604ce05108 (patch) | |
tree | 3d9a5451eb5ac85f8c18619c282eb82eb8fa2ddf /src | |
parent | 369b6a409204a9b2a95f7ba575d7c3b7bdc94ab7 (diff) | |
download | TV-3a72b93e554bd22a5c64e71a6956d9604ce05108.tar.gz |
DO NOT MERGE Revert "DO NOT MERGE Sync to joey ub-tv-dev at e7fbaa585b1eb7afec05f05032d2e8d99fb595d4"
This reverts commit 369b6a409204a9b2a95f7ba575d7c3b7bdc94ab7.
Change-Id: I9abfc39974fa42edcc1eb6b6ec2e53782b4fcf5f
Diffstat (limited to 'src')
113 files changed, 2565 insertions, 5734 deletions
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java index faa27bbd..0a000e9b 100644 --- a/src/com/android/tv/ChannelTuner.java +++ b/src/com/android/tv/ChannelTuner.java @@ -22,12 +22,12 @@ import android.net.Uri; import android.os.Handler; import android.support.annotation.MainThread; import android.support.annotation.Nullable; -import android.util.ArraySet; import android.util.Log; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.CollectionUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; @@ -56,7 +56,7 @@ public class ChannelTuner { private final Handler mHandler = new Handler(); private final ChannelDataManager mChannelDataManager; - private final Set<Listener> mListeners = new ArraySet<>(); + private final Set<Listener> mListeners = CollectionUtils.createSmallSet(); @Nullable private Channel mCurrentChannel; private final TvInputManagerHelper mInputManager; diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java index 6a78b632..1a665506 100644 --- a/src/com/android/tv/Features.java +++ b/src/com/android/tv/Features.java @@ -16,21 +16,20 @@ package com.android.tv; -import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; -import static com.android.tv.common.feature.FeatureUtils.AND; -import static com.android.tv.common.feature.FeatureUtils.ON; -import static com.android.tv.common.feature.FeatureUtils.OR; - -import android.content.Context; -import android.os.Build; import android.support.annotation.VisibleForTesting; -import android.support.v4.os.BuildCompat; import com.android.tv.common.feature.Feature; import com.android.tv.common.feature.GServiceFeature; import com.android.tv.common.feature.PackageVersionFeature; import com.android.tv.common.feature.PropertyFeature; -import com.android.tv.util.PermissionUtils; +import com.android.tv.common.feature.SharedPreferencesFeature; +import com.android.tv.common.feature.TestableFeature; + +import static com.android.tv.common.feature.FeatureUtils.AND; +import static com.android.tv.common.feature.FeatureUtils.ON; +import static com.android.tv.common.feature.FeatureUtils.OR; +import static com.android.tv.common.feature.TestableFeature.createTestableFeature; +import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; /** * List of {@link Feature} for the Live TV App. @@ -44,65 +43,47 @@ public final class Features { * <p>Do not turn this on until the splash screen asking existing users to opt-in is launched. * See <a href="http://b/20228119">b/20228119</a> */ - public static final Feature ANALYTICS_OPT_IN = ENG_ONLY_FEATURE; + public static Feature ANALYTICS_OPT_IN = ENG_ONLY_FEATURE; /** * Analytics that include sensitive information such as channel or program identifiers. * * <p>See <a href="http://b/22062676">b/22062676</a> */ - public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN); + public static Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN); - public static final Feature EPG_SEARCH = - new PropertyFeature("feature_tv_use_epg_search", false); + public static Feature EPG_SEARCH = new PropertyFeature("feature_tv_use_epg_search", false); - public static final Feature USB_TUNER = new Feature() { - - /** - * This is special handling just for USB Tuner. - * It does not require any N API's but relies on a improvements in N for AC3 support - * After release, change class to this to just be - * {@link BuildCompat#isAtLeastN()}. - */ - @Override - public boolean isEnabled(Context context) { - return Build.VERSION.SDK_INT > Build.VERSION_CODES.M || BuildCompat.isAtLeastN(); - } - - }; + public static SharedPreferencesFeature USB_TUNER = new SharedPreferencesFeature( + "usb_tuner", true, + OR(ENG_ONLY_FEATURE, new GServiceFeature("usbtuner_enabled", false))); + public static Feature DEVELOPER_OPTION = OR(ENG_ONLY_FEATURE, + new GServiceFeature("usbtuner_enabled", false)); private static final String PLAY_STORE_PACKAGE_NAME = "com.android.vending"; private static final int PLAY_STORE_ZIMA_VERSION_CODE = 80441186; - private static final Feature PLAY_STORE_LINK = - new PackageVersionFeature(PLAY_STORE_PACKAGE_NAME, PLAY_STORE_ZIMA_VERSION_CODE); + private static Feature PLAY_STORE_LINK = new PackageVersionFeature(PLAY_STORE_PACKAGE_NAME, + PLAY_STORE_ZIMA_VERSION_CODE); - public static final Feature ONBOARDING_PLAY_STORE = PLAY_STORE_LINK; + public static Feature ONBOARDING_PLAY_STORE = PLAY_STORE_LINK; /** * A flag which indicates that the on-boarding experience is used or not. * * <p>See <a href="http://b/24070322">b/24070322</a> */ - public static final Feature ONBOARDING_EXPERIENCE = ONBOARDING_PLAY_STORE; + public static Feature ONBOARDING_EXPERIENCE = ONBOARDING_PLAY_STORE; private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide"; /** * A flag which indicates that LC app is unhidden even when there is no input. */ - public static final Feature UNHIDE = AND(ONBOARDING_EXPERIENCE, - OR(new GServiceFeature(GSERVICE_KEY_UNHIDE, false), new Feature() { - @Override - public boolean isEnabled(Context context) { - // If LC app runs as non-system app, we unhide the app. - return !PermissionUtils.hasAccessAllEpg(context); - } - })); + public static Feature UNHIDE = AND(ONBOARDING_EXPERIENCE, + new GServiceFeature(GSERVICE_KEY_UNHIDE, false)); @VisibleForTesting public static Feature TEST_FEATURE = new PropertyFeature("test_feature", false); - public static final Feature FETCH_EPG = new PropertyFeature("live_channels_fetch_epg", false); - private Features() { } } diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 78fda42a..99bcb125 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -40,7 +40,6 @@ import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; -import android.media.tv.TvInputManager.TvInputCallback; import android.media.tv.TvTrackInfo; import android.media.tv.TvView.OnUnhandledInputEventListener; import android.net.Uri; @@ -53,7 +52,6 @@ import android.provider.Settings; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.os.BuildCompat; import android.text.TextUtils; import android.util.Log; import android.view.Display; @@ -75,12 +73,10 @@ import com.android.tv.analytics.SendConfigInfoRunnable; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; import com.android.tv.common.MemoryManageable; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonUtils; import com.android.tv.common.TvContentRatingCache; import com.android.tv.common.WeakHandler; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.recording.RecordedProgram; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.OnCurrentProgramUpdatedListener; @@ -93,7 +89,7 @@ import com.android.tv.dialog.SafeDismissDialogFragment; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrPlayActivity; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.Recording; import com.android.tv.menu.Menu; import com.android.tv.onboarding.OnboardingActivity; import com.android.tv.parental.ContentRatingsManager; @@ -129,13 +125,11 @@ import com.android.tv.util.PipInputManager.PipInput; import com.android.tv.util.RecurringRunner; import com.android.tv.util.SearchManagerHelper; import com.android.tv.util.SetupUtils; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.TvSettings; import com.android.tv.util.TvSettings.PipSound; -import com.android.usbtuner.UsbTunerPreferences; -import com.android.usbtuner.setup.TunerSetupActivity; -import com.android.usbtuner.tvinput.UsbTunerTvInputService; import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; @@ -146,6 +140,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -273,7 +268,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private MediaSession mMediaSession; private int mNowPlayingCardWidth; private int mNowPlayingCardHeight; - private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener(); private String mInputIdUnderSetup; private boolean mIsSetupActivityCalledByPopup; @@ -287,7 +281,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private boolean mDebugNonFullSizeScreen; private boolean mActivityResumed; private boolean mActivityStarted; - private boolean mShouldTuneToTunerChannel; + private boolean mLaunchedByLauncher; private boolean mUseKeycodeBlacklist; private boolean mShowLockedChannelsTemporarily; private boolean mBackKeyPressed; @@ -296,8 +290,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private boolean mAc3PassthroughSupported; private boolean mShowNewSourcesFragment = true; private Uri mRecordingUri; - private String mUsbTunerInputId; - private boolean mOtherActivityLaunched; private boolean mIsFilmModeSet; private float mDefaultRefreshRate; @@ -331,7 +323,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // A caller which started this activity. (e.g. TvSearch) private String mSource; - private final Handler mHandler = new MainActivityHandler(this); + private Handler mHandler = new MainActivityHandler(this); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -375,7 +367,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC Channel channel = mTvView.getCurrentChannel(); if (channel != null && channel.getId() == channelId) { updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - updateMediaSession(); } } }; @@ -422,19 +413,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC }; private ProgramGuideSearchFragment mSearchFragment; - private TvInputCallback mTvInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - if (mUsbTunerInputId.equals(inputId) - && UsbTunerPreferences.shouldShowSetupActivity(MainActivity.this)) { - Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this); - startActivity(intent); - UsbTunerPreferences.setShouldShowSetupActivity(MainActivity.this, false); - SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mUsbTunerInputId); - } - } - }; - private void applyParentalControlSettings() { boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings() .isParentalControlsEnabled(); @@ -446,19 +424,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC protected void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG,"onCreate()"); super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M - && !PermissionUtils.hasAccessAllEpg(this)) { - Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show(); - finish(); - return; - } - boolean skipToShowOnboarding = getIntent().getAction() == Intent.ACTION_VIEW - && TvContract.isChannelUriForPassthroughInput(getIntent().getData()); + if (Features.ONBOARDING_EXPERIENCE.isEnabled(this) - && OnboardingUtils.needToShowOnboarding(this) && !skipToShowOnboarding + && OnboardingUtils.needToShowOnboarding(this) && !TvCommonUtils.isRunningInTest()) { - // TODO: The onboarding is turned off in test, because tests are broken by the - // onboarding. We need to enable the feature for tests later. + // TODO: We turn off the new onboarding for test, because tests are broken by + // the new onboarding. We need to enable the feature for tests later. startActivity(OnboardingActivity.buildIntent(this, getIntent())); finish(); return; @@ -471,8 +442,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } mTracker = tvApplication.getTracker(); mTvInputManagerHelper = tvApplication.getTvInputManagerHelper(); - mTvInputManagerHelper.addCallback(mTvInputCallback); - mUsbTunerInputId = UsbTunerTvInputService.getInputId(this); mChannelDataManager = tvApplication.getChannelDataManager(); mProgramDataManager = tvApplication.getProgramDataManager(); mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID, @@ -486,7 +455,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mMemoryManageables.add(mProgramDataManager); mMemoryManageables.add(ImageCache.getInstance()); mMemoryManageables.add(TvContentRatingCache.getInstance()); - if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) { + if(CommonFeatures.DVR.isEnabled(this)) { mDvrManager = tvApplication.getDvrManager(); mDvrDataManager = tvApplication.getDvrDataManager(); } @@ -533,7 +502,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC new OnCurrentProgramUpdatedListener() { @Override public void onCurrentProgramUpdated(long channelId, Program program) { - updateMediaSession(); switch (mTimeShiftManager.getLastActionId()) { case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND: case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD: @@ -650,10 +618,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mSendConfigInfoRecurringRunner.start(); mChannelStatusRecurringRunner = SendChannelStatusRunnable .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager); - - // To avoid not updating Rating systems when changing language. - mTvInputManagerHelper.getContentRatingsManager().update(); - initForTest(); } @@ -661,12 +625,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) { - if (grantResults != null && grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Start reload of dependent data - mChannelDataManager.reload(); - mProgramDataManager.reload(); - + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Restart live channels. Intent intent = getIntent(); finish(); @@ -760,11 +719,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC protected void onResume() { if (DEBUG) Log.d(TAG, "onResume()"); super.onResume(); - if (!PermissionUtils.hasAccessAllEpg(this) - && checkSelfPermission(PERMISSION_READ_TV_LISTINGS) + if (!PermissionUtils.hasAccessAllEpg(this)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show(); + finish(); + } else if (checkSelfPermission(PERMISSION_READ_TV_LISTINGS) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, - PERMISSIONS_REQUEST_READ_TV_LISTINGS); + requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, + PERMISSIONS_REQUEST_READ_TV_LISTINGS); + } } mTracker.sendScreenView(SCREEN_NAME); @@ -772,7 +735,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mNeedShowBackKeyGuide = true; mActivityResumed = true; mShowNewSourcesFragment = true; - mOtherActivityLaunched = false; int result = mAudioManager.requestAudioFocus(MainActivity.this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? @@ -836,7 +798,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } mBackKeyPressed = false; mShowLockedChannelsTemporarily = false; - mShouldTuneToTunerChannel = false; + mLaunchedByLauncher = false; if (!mVisibleBehind) { mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; mAudioManager.abandonAudioFocus(this); @@ -874,7 +836,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private void resumeTvIfNeeded() { if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()"); if (!mTvView.isPlaying() || mInitChannelUri != null - || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) { + || (mLaunchedByLauncher && mChannelTuner.isCurrentChannelPassthrough())) { if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) { // The target input may not be ready yet, especially, just after screen on. String inputId = mInitChannelUri.getPathSegments().get(1); @@ -1117,15 +1079,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } public Channel getCurrentChannel() { - return mTvView.isRecordingPlayback() ? mTvView.getCurrentChannel() - : mChannelTuner.getCurrentChannel(); + return mChannelTuner.getCurrentChannel(); } public long getCurrentChannelId() { - if (mTvView.isRecordingPlayback()) { - Channel channel = mTvView.getCurrentChannel(); - return channel == null ? Channel.INVALID_ID : channel.getId(); - } return mChannelTuner.getCurrentChannelId(); } @@ -1142,31 +1099,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC * If the time shifting is available, it can be a past program. */ public Program getCurrentProgram() { - return getCurrentProgram(true); - } - - /** - * Returns {@code true}, if this view is the recording playback mode. - */ - public boolean isRecordingPlayback() { - return mTvView.isRecordingPlayback(); - } - - /** - * Returns the recording which is being played right now. - */ - public RecordedProgram getPlayingRecordedProgram() { - return mTvView.getPlayingRecordedProgram(); - } - - /** - * Returns the current program which the user is watching right now.<p> - * - * @param applyTimeShifted If it is true and the time shifting is available, it can be - * a past program. - */ - public Program getCurrentProgram(boolean applyTimeShifted) { - if (applyTimeShifted && mTimeShiftManager.isAvailable()) { + if (mTimeShiftManager.isAvailable()) { return mTimeShiftManager.getCurrentProgram(); } return mProgramDataManager.getCurrentProgram(getCurrentChannelId()); @@ -1439,7 +1372,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC onKeyUp(keyCode, event); return true; } - mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false); + mLaunchedByLauncher = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false); mInitChannelUri = null; String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION); @@ -1454,24 +1387,14 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) { + if (CommonFeatures.DVR.isEnabled(this)) { mRecordingUri = intent.getParcelableExtra(Utils.EXTRA_KEY_RECORDING_URI); if (mRecordingUri != null) { return true; } } - // TODO: remove the checkState once N API is finalized. - SoftPreconditions.checkState(TvInputManager.ACTION_SETUP_INPUTS.equals( - "android.media.tv.action.SETUP_INPUTS")); - if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) { - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - mOverlayManager.showSetupFragment(); - } - }); - } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { + if (Intent.ACTION_VIEW.equals(intent.getAction())) { Uri uri = intent.getData(); try { mSource = uri.getQueryParameter(Utils.PARAM_SOURCE); @@ -1493,7 +1416,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (Channels.CONTENT_URI.equals(mInitChannelUri)) { // Tune to default channel. mInitChannelUri = null; - mShouldTuneToTunerChannel = true; return true; } if ((!Utils.isChannelUriForOneChannel(mInitChannelUri) @@ -1561,7 +1483,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private void setVolumeByAudioFocusStatus(TunableTvView tvView) { - SoftPreconditions.checkState(tvView == mTvView || tvView == mPipView); if (tvView.isPlaying()) { switch (mAudioFocusStatus) { case AudioManager.AUDIOFOCUS_GAIN: @@ -1576,15 +1497,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC break; } } - if (tvView == mTvView) { - if (mPipView != null && mPipView.isPlaying()) { - mPipView.setStreamVolume(AUDIO_MIN_VOLUME); - } - } else { // tvView == mPipView - if (mTvView != null && mTvView.isPlaying()) { - mTvView.setStreamVolume(AUDIO_MIN_VOLUME); - } - } } private void stopTv() { @@ -1689,7 +1601,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mPipView.setMain(); scheduleRestoreMainTvView(); mTvViewUiManager.onPipStart(); - setVolumeByAudioFocusStatus(); + mPipView.setStreamVolume(AUDIO_MIN_VOLUME); } private void scheduleRestoreMainTvView() { @@ -1721,9 +1633,27 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private void playRecording(Uri recordingUri) { - mTvView.playRecording(recordingUri, mOnTuneListener); - mOnTuneListener.onPlayRecording(); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); + String inputId = recordingUri.getQueryParameter(Recording.PARAM_INPUT_ID); + SoftPreconditions.checkNotNull(inputId); + mTvView.playRecording(inputId, recordingUri, new OnTuneListener() { + @Override + public void onTuneFailed(Channel channel) { } + + @Override + public void onUnexpectedStop(Channel channel) { } + + @Override + public void onStreamInfoChanged(StreamInfo info) { } + + @Override + public void onChannelRetuned(Uri channel) { } + + @Override + public void onContentBlocked() { } + + @Override + public void onContentAllowed() { } + }); } private void tune() { @@ -1738,73 +1668,75 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return; } mTunePending = false; - final Channel channel = mChannelTuner.getCurrentChannel(); - if (!mChannelTuner.isCurrentChannelPassthrough()) { - if (mTvInputManagerHelper.getTunerTvInputSize() == 0) { - Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show(); - // TODO: Direct the user to a Play Store landing page for TvInputService apps. - finish(); + if (!mChannelTuner.isCurrentChannelPassthrough() + && mTvInputManagerHelper.getTunerTvInputSize() == 0) { + Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show(); + // TODO: Direct the user to a Play Store landing page for TvInputService apps. + finish(); + return; + } + SetupUtils setupUtils = SetupUtils.getInstance(this); + if (!mChannelTuner.isCurrentChannelPassthrough() && setupUtils.isFirstTune()) { + if (!mChannelTuner.areAllChannelsLoaded()) { + // tune() will be called, once all channels are loaded. + stopTv("tune()", false); return; } - SetupUtils setupUtils = SetupUtils.getInstance(this); - if (setupUtils.isFirstTune()) { - if (!mChannelTuner.areAllChannelsLoaded()) { - // tune() will be called, once all channels are loaded. - stopTv("tune()", false); - return; - } - if (mChannelDataManager.getChannelCount() > 0) { - mOverlayManager.showIntroDialog(); - } else if (!Features.ONBOARDING_EXPERIENCE.isEnabled(this)) { - mOverlayManager.showSetupFragment(); - return; + if (mChannelDataManager.getChannelCount() > 0) { + mOverlayManager.showIntroDialog(); + } else if (!Features.ONBOARDING_EXPERIENCE.isEnabled(this)) { + mOverlayManager.showSetupFragment(); + return; + } + } + if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment + && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { + // Show new channel sources fragment. + runAfterAttachedToWindow(new Runnable() { + @Override + public void run() { + mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { + @Override + public void run() { + mOverlayManager.showNewSourcesFragment(); + } + }); } + }); + } + mShowNewSourcesFragment = false; + if (!mChannelTuner.isCurrentChannelPassthrough() + && mChannelTuner.getBrowsableChannelCount() == 0 + && mChannelDataManager.getChannelCount() > 0 + && !mOverlayManager.getSideFragmentManager().isActive()) { + if (!mChannelTuner.areAllChannelsLoaded()) { + return; } - if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment - && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { - // Show new channel sources fragment. - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { - @Override - public void run() { - mOverlayManager.showNewSourcesFragment(); - } - }); - } - }); + if (mTvInputManagerHelper.getTunerTvInputSize() == 1) { + mOverlayManager.getSideFragmentManager().show(new CustomizeChannelListFragment()); + } else { + showSettingsFragment(); } - mShowNewSourcesFragment = false; - if (mChannelTuner.getBrowsableChannelCount() == 0 - && mChannelDataManager.getChannelCount() > 0 - && !mOverlayManager.getSideFragmentManager().isActive()) { - if (!mChannelTuner.areAllChannelsLoaded()) { - return; - } - if (mTvInputManagerHelper.getTunerTvInputSize() == 1) { - mOverlayManager.getSideFragmentManager().show( - new CustomizeChannelListFragment()); - } else { - showSettingsFragment(); - } + return; + } + // TODO: need to refactor the following code to put in startTv. + final Channel channel = mChannelTuner.getCurrentChannel(); + if (channel == null) { + // There is no channel to tune to. + stopTv("tune()", false); + if (!mChannelDataManager.isDbLoadFinished()) { + // Wait until channel data is loaded in order to know the number of channels. + // tune() will be retried, once the channel data is loaded. return; } - // TODO: need to refactor the following code to put in startTv. - if (channel == null) { - // There is no channel to tune to. - stopTv("tune()", false); - if (!mChannelDataManager.isDbLoadFinished()) { - // Wait until channel data is loaded in order to know the number of channels. - // tune() will be retried, once the channel data is loaded. - return; - } - if (mOverlayManager.getSideFragmentManager().isActive()) { - return; - } - mOverlayManager.showSetupFragment(); + if (mOverlayManager.getSideFragmentManager().isActive()) { return; } + mOverlayManager.showSetupFragment(); + return; + } + + if (!channel.isPassthrough()) { setupUtils.onTuned(); if (mTuneParams != null) { Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID); @@ -1820,6 +1752,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!isUnderShrunkenTvView()) { mLastAllowedRatingForCurrentChannel = null; } + final boolean wasUnderShrunkenTvView = isUnderShrunkenTvView(); + final long streamInfoUpdateTimeThresholdMs = + System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS; mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE); if (mAccessibilityManager.isEnabled()) { // For every tune, we need to inform the tuned channel or input to a user, @@ -1839,9 +1774,105 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mAccessibilityManager.sendAccessibilityEvent(event); } - boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener); - mOnTuneListener.onTune(channel, isUnderShrunkenTvView()); + boolean success = mTvView.tuneTo(channel, mTuneParams, new OnTuneListener() { + boolean mUnlockAllowedRatingBeforeShrunken = true; + + @Override + public void onUnexpectedStop(Channel channel) { + stopTv(); + startTv(null); + } + + @Override + public void onTuneFailed(Channel channel) { + Log.w(TAG, "Failed to tune to channel " + channel.getId() + + "@" + channel.getInputId()); + if (mTvView.isFadedOut()) { + mTvView.removeFadeEffect(); + } + // TODO: show something to user about this error. + } + @Override + public void onStreamInfoChanged(StreamInfo info) { + if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) { + mTracker.sendChannelTuneTime(info.getCurrentChannel(), + mTuneDurationTimer.reset()); + } + // If updateChannelBanner() is called without delay, the stream info seems flickering + // when the channel is quickly changed. + if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE) + && info.isVideoAvailable()) { + if (System.currentTimeMillis() > streamInfoUpdateTimeThresholdMs) { + updateChannelBannerAndShowIfNeeded( + UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); + } else { + mHandler.sendMessageDelayed(mHandler.obtainMessage( + MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE), + streamInfoUpdateTimeThresholdMs - System.currentTimeMillis()); + } + } + + applyDisplayRefreshRate(info.getVideoFrameRate()); + mTvViewUiManager.updateTvView(); + applyMultiAudio(); + applyClosedCaption(); + // TODO: Send command to TIS with checking the settings in TV and CaptionManager. + mOverlayManager.getMenu().onStreamInfoChanged(); + if (mTvView.isVideoAvailable()) { + mTvViewUiManager.fadeInTvView(); + } + mHandler.removeCallbacks(mRestoreMainViewRunnable); + restoreMainTvView(); + } + + @Override + public void onChannelRetuned(Uri channel) { + if (channel == null) { + return; + } + Channel currentChannel = + mChannelDataManager.getChannel(ContentUris.parseId(channel)); + if (currentChannel == null) { + Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI " + + channel); + return; + } + if (isChannelChangeKeyDownReceived()) { + // Ignore this message if the user is changing the channel. + return; + } + mChannelTuner.setCurrentChannel(currentChannel); + mTvView.setCurrentChannel(currentChannel); + updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); + } + + @Override + public void onContentBlocked() { + mTuneDurationTimer.reset(); + TvContentRating rating = mTvView.getBlockedContentRating(); + // When tuneTo was called while TV view was shrunken, if the channel id is the same + // with the channel watched before shrunken, we allow the rating which was allowed + // before. + if (wasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken + && mChannelBeforeShrunkenTvView.equals(channel) + && rating.equals(mAllowedRatingBeforeShrunken)) { + mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView(); + mTvView.requestUnblockContent(rating); + } + + updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + mTvViewUiManager.fadeInTvView(); + } + + @Override + public void onContentAllowed() { + if (!isUnderShrunkenTvView()) { + mUnlockAllowedRatingBeforeShrunken = false; + } + updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + } + }); mTuneParams = null; if (!success) { Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show(); @@ -1938,9 +1969,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private void updateProgramPosterArt(Program program, @Nullable Bitmap posterArt) { - if (getCurrentChannel() == null) { - return; - } if (posterArt != null) { String cardTitleText = program == null ? null : program.getTitle(); if (TextUtils.isEmpty(cardTitleText)) { @@ -1967,15 +1995,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return; } - String cardTitleText; - if (channel.isPassthrough()) { - TvInputInfo input = getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); - cardTitleText = Utils.loadLabel(this, input); - } else { - cardTitleText = program == null ? null : program.getTitle(); - if (TextUtils.isEmpty(cardTitleText)) { - cardTitleText = channel.getDisplayName(); - } + String cardTitleText = program == null ? null : program.getTitle(); + if (TextUtils.isEmpty(cardTitleText)) { + cardTitleText = channel.getDisplayName(); } Bitmap posterArt = BitmapFactory.decodeResource( @@ -2055,7 +2077,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); - if (!mChannelTuner.isCurrentChannelPassthrough() || mTvView.isRecordingPlayback()) { + if (!mChannelTuner.isCurrentChannelPassthrough()) { int lockType = ChannelBannerView.LOCK_NONE; if (mTvView.isScreenBlocked()) { lockType = ChannelBannerView.LOCK_CHANNEL_INFO; @@ -2299,9 +2321,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mChannelStatusRecurringRunner.stop(); mChannelStatusRecurringRunner = null; } - if (mTvInputManagerHelper != null) { - mTvInputManagerHelper.removeCallback(mTvInputCallback); - } super.onDestroy(); } @@ -2454,7 +2473,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC public void done(boolean success) { if (success) { mLastAllowedRatingForCurrentChannel = rating; - mTvView.unblockContent(rating); + mTvView.requestUnblockContent(rating); } } }); @@ -2481,8 +2500,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); } if (keyCode != KeyEvent.KEYCODE_E) { - mOverlayManager.showMenu(mTvView.isRecordingPlayback() - ? Menu.REASON_RECORDING_PLAYBACK : Menu.REASON_NONE); + mOverlayManager.showMenu(Menu.REASON_NONE); } return true; case KeyEvent.KEYCODE_CHANNEL_UP: @@ -2585,18 +2603,17 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC case KeyEvent.KEYCODE_PROG_YELLOW: case KeyEvent.KEYCODE_BUTTON_Y: case KeyEvent.KEYCODE_Y: { - if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) { + if (CommonFeatures.DVR.isEnabled(this)) { // TODO(DVR) only get finished recordings. - List<RecordedProgram> recordedPrograms = mDvrDataManager - .getRecordedPrograms(); - Log.d(TAG, "Found " + recordedPrograms.size() + " recordings"); - if (recordedPrograms.isEmpty()) { + List<Recording> recordings = mDvrDataManager.getRecordings(); + Log.d(TAG, "Found " + recordings.size() + " recordings"); + if (recordings.isEmpty()) { Toast.makeText(this, "No finished recording to play", Toast.LENGTH_LONG) .show(); } else { - RecordedProgram r = recordedPrograms.get(0); + Recording r = recordings.get(0); Intent intent = new Intent(this, DvrPlayActivity.class); - intent.putExtra(ScheduledRecording.RECORDING_ID_EXTRA, r.getId()); + intent.putExtra(Recording.RECORDING_ID_EXTRA, r.getId()); startActivity(intent); } return true; @@ -2647,20 +2664,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - @Override - public void enterPictureInPictureMode() { - // We need to hide overlay first, before moving the activity to PIP. If not, UI will - // be shown during PIP stack resizing, because UI and its animation is stuck during - // PIP resizing. - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); - mHandler.post(new Runnable() { - @Override - public void run() { - MainActivity.super.enterPictureInPictureMode(); - } - }); - } - public void togglePipView() { enablePipView(!mPipEnabled, true); mOverlayManager.getMenu().update(); @@ -2711,6 +2714,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // Recover the stream volume of the main TV view, if needed. if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) { setVolumeByAudioFocusStatus(mTvView); + mPipView.setStreamVolume(AUDIO_MIN_VOLUME); mPipSound = TvSettings.PIP_SOUND_MAIN; mTvOptionsManager.onPipSoundChanged(mPipSound); } @@ -2873,8 +2877,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } if (mPipSound == TvSettings.PIP_SOUND_MAIN) { setVolumeByAudioFocusStatus(mTvView); + mPipView.setStreamVolume(AUDIO_MIN_VOLUME); } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW setVolumeByAudioFocusStatus(mPipView); + mTvView.setStreamVolume(AUDIO_MIN_VOLUME); } mPipSwap = !mPipSwap; mTvOptionsManager.onPipSwapChanged(mPipSwap); @@ -2890,9 +2896,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } if (mPipSound == TvSettings.PIP_SOUND_MAIN) { setVolumeByAudioFocusStatus(mPipView); + mTvView.setStreamVolume(AUDIO_MIN_VOLUME); mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW; } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW setVolumeByAudioFocusStatus(mTvView); + mPipView.setStreamVolume(AUDIO_MIN_VOLUME); mPipSound = TvSettings.PIP_SOUND_MAIN; } restoreMainTvView(); @@ -2921,26 +2929,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } stopPip(); mVisibleBehind = false; - if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { - // Workaround: in M, onStop is not called, even though it should be called after - // onVisibleBehindCanceled is called. As a workaround, we call finish(). - finish(); - } super.onVisibleBehindCanceled(); } - @Override - public void startActivity(Intent intent) { - mOtherActivityLaunched = true; - super.startActivity(intent); - } - - @Override - public void startActivityForResult(Intent intent, int requestCode) { - mOtherActivityLaunched = true; - super.startActivityForResult(intent, requestCode); - } - public List<TvTrackInfo> getTracks(int type) { return mTvView.getTracks(type); } @@ -3024,8 +3015,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: - case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: return; + case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: + stringId = R.string.msg_channel_unavailable_weak_signal; + break; case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: default: stringId = R.string.msg_channel_unavailable_unknown; @@ -3126,123 +3119,4 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED; } } - - private class MyOnTuneListener implements OnTuneListener { - boolean mUnlockAllowedRatingBeforeShrunken = true; - boolean mWasUnderShrunkenTvView; - long mStreamInfoUpdateTimeThresholdMs; - Channel mChannel; - - public MyOnTuneListener() { } - - private void onTune(Channel channel, boolean wasUnderShrukenTvView) { - mStreamInfoUpdateTimeThresholdMs = - System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS; - mChannel = channel; - mWasUnderShrunkenTvView = wasUnderShrukenTvView; - } - - private void onPlayRecording() { - mStreamInfoUpdateTimeThresholdMs = - System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS; - mChannel = null; - mWasUnderShrunkenTvView = false; - } - - @Override - public void onUnexpectedStop(Channel channel) { - stopTv(); - startTv(null); - } - - @Override - public void onTuneFailed(Channel channel) { - Log.w(TAG, "Failed to tune to channel " + channel.getId() - + "@" + channel.getInputId()); - if (mTvView.isFadedOut()) { - mTvView.removeFadeEffect(); - } - // TODO: show something to user about this error. - } - - @Override - public void onStreamInfoChanged(StreamInfo info) { - if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) { - mTracker.sendChannelTuneTime(info.getCurrentChannel(), - mTuneDurationTimer.reset()); - } - // If updateChannelBanner() is called without delay, the stream info seems flickering - // when the channel is quickly changed. - if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE) - && info.isVideoAvailable()) { - if (System.currentTimeMillis() > mStreamInfoUpdateTimeThresholdMs) { - updateChannelBannerAndShowIfNeeded( - UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - } else { - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE), - mStreamInfoUpdateTimeThresholdMs - System.currentTimeMillis()); - } - } - - applyDisplayRefreshRate(info.getVideoFrameRate()); - mTvViewUiManager.updateTvView(); - applyMultiAudio(); - applyClosedCaption(); - // TODO: Send command to TIS with checking the settings in TV and CaptionManager. - mOverlayManager.getMenu().onStreamInfoChanged(); - if (mTvView.isVideoAvailable()) { - mTvViewUiManager.fadeInTvView(); - } - mHandler.removeCallbacks(mRestoreMainViewRunnable); - restoreMainTvView(); - } - - @Override - public void onChannelRetuned(Uri channel) { - if (channel == null) { - return; - } - Channel currentChannel = - mChannelDataManager.getChannel(ContentUris.parseId(channel)); - if (currentChannel == null) { - Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI " - + channel); - return; - } - if (isChannelChangeKeyDownReceived()) { - // Ignore this message if the user is changing the channel. - return; - } - mChannelTuner.setCurrentChannel(currentChannel); - mTvView.setCurrentChannel(currentChannel); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); - } - - @Override - public void onContentBlocked() { - mTuneDurationTimer.reset(); - TvContentRating rating = mTvView.getBlockedContentRating(); - // When tuneTo was called while TV view was shrunken, if the channel id is the same - // with the channel watched before shrunken, we allow the rating which was allowed - // before. - if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken - && mChannelBeforeShrunkenTvView.equals(mChannel) - && rating.equals(mAllowedRatingBeforeShrunken)) { - mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView(); - mTvView.unblockContent(rating); - } - - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); - mTvViewUiManager.fadeInTvView(); - } - - @Override - public void onContentAllowed() { - if (!isUnderShrunkenTvView()) { - mUnlockAllowedRatingBeforeShrunken = false; - } - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); - } - } } diff --git a/src/com/android/tv/MainActivityWrapper.java b/src/com/android/tv/MainActivityWrapper.java index 82e96d14..94f11864 100644 --- a/src/com/android/tv/MainActivityWrapper.java +++ b/src/com/android/tv/MainActivityWrapper.java @@ -20,8 +20,8 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.util.ArraySet; +import com.android.tv.common.CollectionUtils; import com.android.tv.data.Channel; import java.util.Set; @@ -34,7 +34,7 @@ import java.util.Set; public final class MainActivityWrapper { private MainActivity mActivity; - private final Set<OnCurrentChannelChangeListener> mListeners = new ArraySet<>(); + private final Set<OnCurrentChannelChangeListener> mListeners = CollectionUtils.createSmallSet(); /** * Returns the current main activity. diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java index e6373505..bdabf25b 100644 --- a/src/com/android/tv/SetupPassthroughActivity.java +++ b/src/com/android/tv/SetupPassthroughActivity.java @@ -23,9 +23,9 @@ import android.media.tv.TvInputInfo; import android.os.Bundle; import android.util.Log; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonConstants; import com.android.tv.util.SetupUtils; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.TvInputManagerHelper; /** diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java index a231c29d..f96464e3 100644 --- a/src/com/android/tv/TimeShiftManager.java +++ b/src/com/android/tv/TimeShiftManager.java @@ -28,9 +28,7 @@ import android.util.Log; import android.util.Range; import com.android.tv.analytics.Tracker; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; -import com.android.tv.common.recording.RecordedProgram; import com.android.tv.data.Channel; import com.android.tv.data.OnCurrentProgramUpdatedListener; import com.android.tv.data.Program; @@ -181,7 +179,7 @@ public class TimeShiftManager { tvView.setOnScreenBlockedListener(new TunableTvView.OnScreenBlockingChangedListener() { @Override public void onScreenBlockingChanged(boolean blocked) { - mPlayController.onAvailabilityChanged(); + onAvailabilityChanged(); } }); } @@ -197,7 +195,7 @@ public class TimeShiftManager { * Checks if the trick play is available for the current channel. */ public boolean isAvailable() { - return mPlayController.mAvailable; + return mPlayController.isAvailable(); } /** @@ -231,6 +229,11 @@ public class TimeShiftManager { } } + public boolean isPlayForRecording() { + // TODO: need to find better way to check if it's for recording playback. + return mPlayController.mRecordEndTimeMs != CURRENT_TIME; + } + /** * Plays the media. * @@ -467,18 +470,11 @@ public class TimeShiftManager { } /** - * Checks whether the TV is playing the recorded content. - */ - public boolean isRecordingPlayback() { - return mPlayController.mRecordingPlayback; - } - - /** * Returns {@code true} if the trick play is available and it's playing to the forward direction * with normal speed, otherwise {@code false}. */ public boolean isNormalPlaying() { - return mPlayController.mAvailable + return mPlayController.isAvailable() && mPlayController.mPlayStatus == PLAY_STATUS_PLAYING && mPlayController.mPlayDirection == PLAY_DIRECTION_FORWARD && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X; @@ -488,7 +484,7 @@ public class TimeShiftManager { * Checks if the trick play is available and it's playback status is paused. */ public boolean isPaused() { - return mPlayController.mAvailable && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED; + return mPlayController.isAvailable() && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED; } /** @@ -506,9 +502,8 @@ public class TimeShiftManager { } void onAvailabilityChanged() { - mProgramManager.onAvailabilityChanged(mPlayController.mAvailable, - mPlayController.mRecordingPlayback ? null : mPlayController.getCurrentChannel(), - mPlayController.mRecordStartTimeMs); + mProgramManager.onAvailabilityChanged(mPlayController.isAvailable(), + mPlayController.getCurrentChannel(), mPlayController.mRecordStartTimeMs); updateActions(); // Availability change notification should be always sent // even if mNotificationEnabled is false. @@ -518,7 +513,7 @@ public class TimeShiftManager { } void onRecordTimeRangeChanged() { - if (mPlayController.mAvailable) { + if (mPlayController.isAvailable()) { mProgramManager.onRecordTimeRangeChanged(mPlayController.mRecordStartTimeMs, mPlayController.mRecordEndTimeMs); } @@ -595,6 +590,7 @@ public class TimeShiftManager { private class PlayController { private final TunableTvView mTvView; + private long mPossibleStartTimeMs; private long mRecordStartTimeMs; private long mRecordEndTimeMs; @@ -602,8 +598,6 @@ public class TimeShiftManager { @PlaySpeed private int mDisplayedPlaySpeed = PLAY_SPEED_1X; @PlayDirection private int mPlayDirection = PLAY_DIRECTION_FORWARD; private int mPlaybackSpeed; - private boolean mAvailable; - private boolean mRecordingPlayback; /** * Indicates that the trick play is not playing the current time position. @@ -619,11 +613,47 @@ public class TimeShiftManager { mTvView.setTimeShiftListener(new TimeShiftListener() { @Override public void onAvailabilityChanged() { - PlayController.this.onAvailabilityChanged(); + // Do not send the notifications while the availability is changing, + // because the variables are in the intermediate state. + // For example, the current program can be null. + mNotificationEnabled = false; + mDisplayedPlaySpeed = PLAY_SPEED_1X; + mPlaybackSpeed = 1; + mPlayDirection = PLAY_DIRECTION_FORWARD; + mIsPlayOffsetChanged = false; + mPossibleStartTimeMs = System.currentTimeMillis(); + mRecordStartTimeMs = mPossibleStartTimeMs; + mRecordEndTimeMs = CURRENT_TIME; + mCurrentPositionMediator.initialize(mPossibleStartTimeMs); + mHandler.removeMessages(MSG_GET_CURRENT_POSITION); + + if (isAvailable()) { + // When the media availability message has come. + mPlayController.setPlayStatus(PLAY_STATUS_PLAYING); + mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, + REQUEST_CURRENT_POSITION_INTERVAL); + } else { + // When the tune command is sent. + mPlayController.setPlayStatus(PLAY_STATUS_PAUSED); + } + TimeShiftManager.this.onAvailabilityChanged(); + mNotificationEnabled = true; } @Override public void onRecordStartTimeChanged(long recordStartTimeMs) { + if (mRecordEndTimeMs == CURRENT_TIME && + recordStartTimeMs < mPossibleStartTimeMs) { + // Do not warn in this case because it can happen in normal cases. + if (DEBUG) { + Log.d(TAG, "Record start time is less then the time when it became " + + "available. {availableStartTime=" + + Utils.toTimeString(mPossibleStartTimeMs) + + ", recordStartTimeMs=" + Utils.toTimeString(recordStartTimeMs) + + "}"); + } + recordStartTimeMs = mPossibleStartTimeMs; + } if (mRecordStartTimeMs == recordStartTimeMs) { return; } @@ -645,48 +675,26 @@ public class TimeShiftManager { TimeShiftManager.this.play(); } } + + @Override + public void onRecordEndTimeChanged(long recordEndTimeMs) { + if (mRecordEndTimeMs == recordEndTimeMs) { + return; + } + mRecordEndTimeMs = recordEndTimeMs; + TimeShiftManager.this.onRecordTimeRangeChanged(); + + if (mPlayStatus == PLAY_STATUS_PLAYING && + mRecordEndTimeMs - getCurrentPositionMs() + < RECORDING_BOUNDARY_THRESHOLD) { + TimeShiftManager.this.pause(); + } + } }); } - void onAvailabilityChanged() { - boolean newAvailable = mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked(); - if (mAvailable == newAvailable) { - return; - } - mAvailable = newAvailable; - // Do not send the notifications while the availability is changing, - // because the variables are in the intermediate state. - // For example, the current program can be null. - mNotificationEnabled = false; - mDisplayedPlaySpeed = PLAY_SPEED_1X; - mPlaybackSpeed = 1; - mPlayDirection = PLAY_DIRECTION_FORWARD; - mRecordingPlayback = mTvView.isRecordingPlayback(); - if (mRecordingPlayback) { - RecordedProgram recordedProgram = mTvView.getPlayingRecordedProgram(); - SoftPreconditions.checkNotNull(recordedProgram); - mIsPlayOffsetChanged = true; - mRecordStartTimeMs = 0; - mRecordEndTimeMs = recordedProgram.getDurationMillis(); - } else { - mIsPlayOffsetChanged = false; - mRecordStartTimeMs = System.currentTimeMillis(); - mRecordEndTimeMs = CURRENT_TIME; - } - mCurrentPositionMediator.initialize(mRecordStartTimeMs); - mHandler.removeMessages(MSG_GET_CURRENT_POSITION); - - if (mAvailable) { - // When the media availability message has come. - mPlayController.setPlayStatus(PLAY_STATUS_PLAYING); - mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, - REQUEST_CURRENT_POSITION_INTERVAL); - } else { - // When the tune command is sent. - mPlayController.setPlayStatus(PLAY_STATUS_PAUSED); - } - TimeShiftManager.this.onAvailabilityChanged(); - mNotificationEnabled = true; + boolean isAvailable() { + return mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked(); } void handleGetCurrentPosition() { @@ -847,25 +855,18 @@ public class TimeShiftManager { private final List<Program> mPrograms = new ArrayList<>(); private final Queue<Range<Long>> mProgramLoadQueue = new LinkedList<>(); private LoadProgramsForCurrentChannelTask mProgramLoadTask = null; - private int mEmptyFetchCount = 0; ProgramManager(ProgramDataManager programDataManager) { mProgramDataManager = programDataManager; } void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) { - if (DEBUG) { - Log.d(TAG, "onAvailabilityChanged(" + available + "+," + channel + ", " - + currentPositionMs + ")"); - } - mProgramLoadQueue.clear(); if (mProgramLoadTask != null) { mProgramLoadTask.cancel(true); } mHandler.removeMessages(MSG_PREFETCH_PROGRAM); mPrograms.clear(); - mEmptyFetchCount = 0; mChannel = channel; if (channel == null || channel.isPassthrough()) { return; @@ -1132,37 +1133,17 @@ public class TimeShiftManager { } Program lastValidProgram = getLastValidProgram(); if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram); - final long delay; if (lastValidProgram != null) { - delay = lastValidProgram.getEndTimeUtcMillis() + long delay = lastValidProgram.getEndTimeUtcMillis() - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis(); + mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay); + if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays."); } else { - // Since there might not be any program data delay the retry 5 seconds, - // then 30 seconds then 5 minutes - switch (mEmptyFetchCount) { - case 0: - delay = 0; - break; - case 1: - delay = TimeUnit.SECONDS.toMillis(5); - break; - case 2: - delay = TimeUnit.SECONDS.toMillis(30); - break; - default: - delay = TimeUnit.MINUTES.toMillis(5); - break; - } - if (DEBUG) { - Log.d(TAG, - "No last valid program. Already tried " + mEmptyFetchCount + " times"); - } + mHandler.sendEmptyMessage(MSG_PREFETCH_PROGRAM); + if (DEBUG) Log.d(TAG, "Scheduling promptly."); } - mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay); - if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays."); } - // Prefecth programs within PREFETCH_DURATION_FOR_NEXT from now. private void prefetchPrograms() { long startTimeMs; Program lastValidProgram = getLastValidProgram(); @@ -1172,13 +1153,11 @@ public class TimeShiftManager { startTimeMs = lastValidProgram.getEndTimeUtcMillis(); } long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT; - if (startTimeMs <= endTimeMs) { - if (DEBUG) { - Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs) - + ", endTime=" + Utils.toTimeString(endTimeMs) + "}"); - } - mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs)); + if (DEBUG) { + Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs) + + ", endTime=" + Utils.toTimeString(endTimeMs) + "}"); } + mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs)); startTaskIfNeeded(); } @@ -1206,8 +1185,8 @@ public class TimeShiftManager { it.remove(); } } - if (programs == null || programs.isEmpty()) { - mEmptyFetchCount++; + if (programs == null || programs.isEmpty() || mPrograms.isEmpty()) { + mPrograms.addAll(programs); if (addDummyPrograms(mPeriod)) { TimeShiftManager.this.onProgramInfoChanged(); } @@ -1215,22 +1194,19 @@ public class TimeShiftManager { startNextLoadingIfNeeded(); return; } - mEmptyFetchCount = 0; - if(!mPrograms.isEmpty()) { - removeDummyPrograms(); - removeOverlappedPrograms(programs); - Program loadedProgram = programs.get(0); - for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) { - Program program = mPrograms.get(i); - while (program.getStartTimeUtcMillis() > loadedProgram - .getStartTimeUtcMillis()) { - mPrograms.add(i++, loadedProgram); - programs.remove(0); - if (programs.isEmpty()) { - break; - } - loadedProgram = programs.get(0); + removeDummyPrograms(); + removeOverlappedPrograms(programs); + Program loadedProgram = programs.get(0); + for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) { + Program program = mPrograms.get(i); + while (program.getStartTimeUtcMillis() > loadedProgram + .getStartTimeUtcMillis()) { + mPrograms.add(i++, loadedProgram); + programs.remove(0); + if (programs.isEmpty()) { + break; } + loadedProgram = programs.get(0); } } mPrograms.addAll(programs); diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java index ef105c94..0cac4a3b 100644 --- a/src/com/android/tv/TvApplication.java +++ b/src/com/android/tv/TvApplication.java @@ -16,7 +16,6 @@ package com.android.tv; -import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.content.ComponentName; @@ -24,15 +23,14 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; -import android.os.Build; import android.os.Bundle; import android.os.StrictMode; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.os.BuildCompat; +import android.support.annotation.UiThread; import android.util.Log; import android.view.KeyEvent; @@ -49,17 +47,14 @@ import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ProgramDataManager; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManagerImpl; +import com.android.tv.dvr.DvrDataManagerInMemoryImpl; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrRecordingService; import com.android.tv.dvr.DvrSessionManager; -import com.android.tv.util.Clock; import com.android.tv.util.SetupUtils; import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; -import com.android.usbtuner.UsbTunerPreferences; -import com.android.usbtuner.setup.TunerSetupActivity; -import com.android.usbtuner.tvinput.UsbTunerTvInputService; import java.util.List; @@ -132,7 +127,7 @@ public class TvApplication extends Application implements ApplicationSingletons handleInputCountChanged(); } }); - if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) { + if (CommonFeatures.DVR.isEnabled(this)) { mDvrManager = new DvrManager(this); //NOTE: DvrRecordingService just keeps running. DvrRecordingService.startService(this); @@ -153,7 +148,6 @@ public class TvApplication extends Application implements ApplicationSingletons } @Override - @TargetApi(Build.VERSION_CODES.N) public DvrSessionManager getDvrSessionManger() { if (mDvrSessionManager == null) { mDvrSessionManager = new DvrSessionManager(this); @@ -205,13 +199,16 @@ public class TvApplication extends Application implements ApplicationSingletons /** * Returns {@link DvrDataManager}. */ - @TargetApi(Build.VERSION_CODES.N) @Override public DvrDataManager getDvrDataManager() { if (mDvrDataManager == null) { - DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this, Clock.SYSTEM); + if(SystemProperties.USE_IN_MEMORY_DVR_DB.getValue()){ + mDvrDataManager = new DvrDataManagerInMemoryImpl(this); + } else { + DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this); mDvrDataManager = dvrDataManager; dvrDataManager.start(); + } } return mDvrDataManager; } @@ -259,9 +256,7 @@ public class TvApplication extends Application implements ApplicationSingletons boolean hasTunerInput = false; for (TvInputInfo input : tvInputs) { if (input.isPassthroughInput()) { - if (!input.isHidden(this)) { - ++inputCount; - } + ++inputCount; } else if (!hasTunerInput) { hasTunerInput = true; ++inputCount; @@ -315,38 +310,17 @@ public class TvApplication extends Application implements ApplicationSingletons * {@link SetupUtils}. */ public void handleInputCountChanged() { - handleInputCountChanged(false, false, false); - } - - /** - * Checks the input counts and enable/disable TvActivity. Also updates the input list in - * {@link SetupUtils}. - * - * @param calledByTunerServiceChanged true if it is called when UsbTunerTvInputService - * is enabled or disabled. - * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true. - * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts - * by default. But, if dontKillApp is true, the app won't restart. - */ - public void handleInputCountChanged(boolean calledByTunerServiceChanged, - boolean tunerServiceEnabled, boolean dontKillApp) { TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); - boolean enable = (calledByTunerServiceChanged && tunerServiceEnabled) - || Features.UNHIDE.isEnabled(TvApplication.this); - if (!enable) { + boolean enable = false; + if (Features.UNHIDE.isEnabled(TvApplication.this)) { + enable = true; + } else { List<TvInputInfo> inputs = inputManager.getTvInputList(); - boolean skipTunerInputCheck = false; // Enable the TvActivity only if there is at least one tuner type input. - if (!skipTunerInputCheck) { - for (TvInputInfo input : inputs) { - if (calledByTunerServiceChanged && !tunerServiceEnabled - && UsbTunerTvInputService.getInputId(this).equals(input.getId())) { - continue; - } - if (input.getType() == TvInputInfo.TYPE_TUNER) { - enable = true; - break; - } + for (TvInputInfo input : inputs) { + if (input.getType() == TvInputInfo.TYPE_TUNER) { + enable = true; + break; } } if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable); @@ -356,8 +330,7 @@ public class TvApplication extends Application implements ApplicationSingletons int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; if (packageManager.getComponentEnabledSetting(name) != newState) { - packageManager.setComponentEnabledSetting(name, newState, - dontKillApp ? PackageManager.DONT_KILL_APP : 0); + packageManager.setComponentEnabledSetting(name, newState, 0); } SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager); } diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java index f104e75d..97b9d5fa 100644 --- a/src/com/android/tv/TvOptionsManager.java +++ b/src/com/android/tv/TvOptionsManager.java @@ -35,11 +35,10 @@ import java.util.Locale; public class TvOptionsManager { public static final int OPTION_CLOSED_CAPTIONS = 0; public static final int OPTION_DISPLAY_MODE = 1; - public static final int OPTION_IN_APP_PIP = 2; - public static final int OPTION_SYSTEMWIDE_PIP = 3; - public static final int OPTION_MULTI_AUDIO = 4; - public static final int OPTION_MORE_CHANNELS = 5; - public static final int OPTION_SETTINGS = 6; + public static final int OPTION_PIP = 2; + public static final int OPTION_MULTI_AUDIO = 3; + public static final int OPTION_MORE_CHANNELS = 4; + public static final int OPTION_SETTINGS = 5; public static final int OPTION_PIP_INPUT = 100; public static final int OPTION_PIP_SWAP = 101; @@ -76,7 +75,7 @@ public class TvOptionsManager { .isDisplayModeAvailable(mDisplayMode) ? DisplayMode.getLabel(mDisplayMode, mContext) : DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext); - case OPTION_IN_APP_PIP: + case OPTION_PIP: return mContext.getString( mPip ? R.string.options_item_pip_on : R.string.options_item_pip_off); case OPTION_MULTI_AUDIO: @@ -131,7 +130,7 @@ public class TvOptionsManager { public void onPipChanged(boolean pip) { mPip = pip; - notifyOptionChanged(OPTION_IN_APP_PIP); + notifyOptionChanged(OPTION_PIP); } public void onMultiAudioChanged(String multiAudio) { diff --git a/src/com/android/tv/analytics/SendConfigInfoRunnable.java b/src/com/android/tv/analytics/SendConfigInfoRunnable.java index 41392a6d..c2d5c5fb 100644 --- a/src/com/android/tv/analytics/SendConfigInfoRunnable.java +++ b/src/com/android/tv/analytics/SendConfigInfoRunnable.java @@ -26,8 +26,8 @@ import java.util.List; * Sends ConfigurationInfo once a day. */ public class SendConfigInfoRunnable implements Runnable { - private final Tracker mTracker; - private final TvInputManagerHelper mTvInputManagerHelper; + private Tracker mTracker; + private TvInputManagerHelper mTvInputManagerHelper; public SendConfigInfoRunnable(Tracker tracker, TvInputManagerHelper tvInputManagerHelper) { this.mTracker = tracker; diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java index 86437ab2..ba3c59ba 100644 --- a/src/com/android/tv/data/Channel.java +++ b/src/com/android/tv/data/Channel.java @@ -33,6 +33,7 @@ import android.util.Log; import com.android.tv.common.CollectionUtils; import com.android.tv.common.TvCommonConstants; +import com.android.tv.dvr.provider.DvrContract; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -74,17 +75,17 @@ public final class Channel { private static final String INVALID_PACKAGE_NAME = "packageName"; private static final String[] PROJECTION_BASE = { - // Columns must match what is read in Channel.fromCursor() - TvContract.Channels._ID, - TvContract.Channels.COLUMN_PACKAGE_NAME, - TvContract.Channels.COLUMN_INPUT_ID, - TvContract.Channels.COLUMN_TYPE, - TvContract.Channels.COLUMN_DISPLAY_NUMBER, - TvContract.Channels.COLUMN_DISPLAY_NAME, - TvContract.Channels.COLUMN_DESCRIPTION, - TvContract.Channels.COLUMN_VIDEO_FORMAT, - TvContract.Channels.COLUMN_BROWSABLE, - TvContract.Channels.COLUMN_LOCKED, + // Columns must match what is read in Channel.fromCursor() + TvContract.Channels._ID, + TvContract.Channels.COLUMN_PACKAGE_NAME, + TvContract.Channels.COLUMN_INPUT_ID, + TvContract.Channels.COLUMN_TYPE, + TvContract.Channels.COLUMN_DISPLAY_NUMBER, + TvContract.Channels.COLUMN_DISPLAY_NAME, + TvContract.Channels.COLUMN_DESCRIPTION, + TvContract.Channels.COLUMN_VIDEO_FORMAT, + TvContract.Channels.COLUMN_BROWSABLE, + TvContract.Channels.COLUMN_LOCKED, }; // Additional fields added in MNC. @@ -109,6 +110,15 @@ public final class Channel { } /** + * Use this projection if you want to create {@link Channel} object using + * {@link #fromDvrCursor}. + */ + public static final String[] PROJECTION_DVR = { + // Columns must match what is read in Channel.fromDvrCursor() + DvrContract.DvrChannels._ID + }; + + /** * Creates {@code Channel} object from cursor. * * <p>The query that created the cursor MUST use {@link #PROJECTION} @@ -254,13 +264,6 @@ public final class Channel { } /** - * Checks whether this channel is physical tuner channel or not. - */ - public boolean isPhysicalTunerChannel() { - return !TextUtils.isEmpty(mType) && !TvContract.Channels.TYPE_OTHER.equals(mType); - } - - /** * Checks if two channels equal by checking ids. */ @Override diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java index 84a16111..82ac4b5a 100644 --- a/src/com/android/tv/data/ChannelDataManager.java +++ b/src/com/android/tv/data/ChannelDataManager.java @@ -31,15 +31,15 @@ import android.os.Message; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; -import android.util.ArraySet; import android.util.Log; import android.util.MutableInt; +import com.android.tv.common.CollectionUtils; import com.android.tv.common.SharedPreferencesUtils; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.PermissionUtils; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -72,7 +72,7 @@ public class ChannelDataManager { private QueryAllChannelsTask mChannelsUpdateTask; private final List<Runnable> mPostRunnablesAfterChannelUpdate = new ArrayList<>(); - private final Set<Listener> mListeners = new ArraySet<>(); + private final Set<Listener> mListeners = CollectionUtils.createSmallSet(); private final Map<Long, ChannelWrapper> mChannelWrapperMap = new HashMap<>(); private final Map<String, MutableInt> mChannelCountMap = new HashMap<>(); private final Channel.DefaultComparator mChannelComparator; @@ -282,7 +282,7 @@ public class ChannelDataManager { channels.add(channel); } } - return channels; + return Collections.unmodifiableList(channels); } /** @@ -508,15 +508,6 @@ public class ChannelDataManager { mChannelsUpdateTask.executeOnDbThread(); } - /** - * Reloads channel data. - */ - public void reload() { - if (mDbLoadFinished && !mHandler.hasMessages(MSG_UPDATE_CHANNELS)) { - mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS); - } - } - public interface Listener { /** * Called when data load is finished. @@ -548,7 +539,7 @@ public class ChannelDataManager { } private class ChannelWrapper { - final Set<ChannelListener> mChannelListeners = new ArraySet<>(); + final Set<ChannelListener> mChannelListeners = CollectionUtils.createSmallSet(); final Channel mChannel; boolean mBrowsableInDb; boolean mLockedInDb; diff --git a/src/com/android/tv/data/GenreItems.java b/src/com/android/tv/data/GenreItems.java index b1110612..92e38809 100644 --- a/src/com/android/tv/data/GenreItems.java +++ b/src/com/android/tv/data/GenreItems.java @@ -17,11 +17,13 @@ package com.android.tv.data; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; import android.media.tv.TvContract.Programs.Genres; import android.os.Build; import com.android.tv.R; +import com.android.tv.common.CollectionUtils; public class GenreItems { /** @@ -29,7 +31,7 @@ public class GenreItems { */ public static final int ID_ALL_CHANNELS = 0; - private static final String[] CANONICAL_GENRES_L = { + private static final String[] CANONICAL_GENRES_BASE = { null, // All channels Genres.FAMILY_KIDS, Genres.SPORTS, @@ -45,34 +47,23 @@ public class GenreItems { }; @SuppressLint("InlinedApi") - private static final String[] CANONICAL_GENRES_L_MR1 = { - null, // All channels - Genres.FAMILY_KIDS, - Genres.SPORTS, - Genres.SHOPPING, - Genres.MOVIES, - Genres.COMEDY, - Genres.TRAVEL, - Genres.DRAMA, - Genres.EDUCATION, - Genres.ANIMAL_WILDLIFE, - Genres.NEWS, - Genres.GAMING, - Genres.ARTS, - Genres.ENTERTAINMENT, - Genres.LIFE_STYLE, - Genres.MUSIC, - Genres.PREMIER, - Genres.TECH_SCIENCE + private static final String[] CANONICAL_GENRES_ADDED_IN_L_MR1 = { + Genres.ARTS, + Genres.ENTERTAINMENT, + Genres.LIFE_STYLE, + Genres.MUSIC, + Genres.PREMIER, + Genres.TECH_SCIENCE }; private static final String[] CANONICAL_GENRES = createGenres(); private static String[] createGenres() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - return CANONICAL_GENRES_L; + return CANONICAL_GENRES_BASE; } else { - return CANONICAL_GENRES_L_MR1; + return CollectionUtils + .concatAll(CANONICAL_GENRES_BASE, CANONICAL_GENRES_ADDED_IN_L_MR1); } } @@ -82,9 +73,7 @@ public class GenreItems { * Returns array of all genre labels. */ public static String[] getLabels(Context context) { - String[] items = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 - ? context.getResources().getStringArray(R.array.genre_labels_l) - : context.getResources().getStringArray(R.array.genre_labels_l_mr1); + String[] items = context.getResources().getStringArray(R.array.genre_labels); if (items.length != CANONICAL_GENRES.length) { throw new IllegalArgumentException("Genre data mismatch"); } diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java index af5f93bb..b9c54aac 100644 --- a/src/com/android/tv/data/Program.java +++ b/src/com/android/tv/data/Program.java @@ -22,14 +22,13 @@ import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.support.annotation.NonNull; import android.support.annotation.UiThread; -import android.support.v4.os.BuildCompat; import android.text.TextUtils; import android.util.Log; import com.android.tv.R; import com.android.tv.common.BuildConfig; -import com.android.tv.common.CollectionUtils; import com.android.tv.common.TvContentRatingCache; +import com.android.tv.dvr.provider.DvrContract; import com.android.tv.util.ImageLoader; import com.android.tv.util.Utils; @@ -44,43 +43,33 @@ public final class Program implements Comparable<Program> { private static final boolean DEBUG_DUMP_DESCRIPTION = false; private static final String TAG = "Program"; - private static final String[] PROJECTION_BASE = { - // Columns must match what is read in Program.fromCursor() - TvContract.Programs._ID, - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_EPISODE_TITLE, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_POSTER_ART_URI, - TvContract.Programs.COLUMN_THUMBNAIL_URI, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_VIDEO_WIDTH, - TvContract.Programs.COLUMN_VIDEO_HEIGHT + public static final String[] PROJECTION = { + // Columns must match what is read in Program.fromCursor() + TvContract.Programs.COLUMN_CHANNEL_ID, + TvContract.Programs.COLUMN_TITLE, + TvContract.Programs.COLUMN_EPISODE_TITLE, + TvContract.Programs.COLUMN_SEASON_NUMBER, + TvContract.Programs.COLUMN_EPISODE_NUMBER, + TvContract.Programs.COLUMN_SHORT_DESCRIPTION, + TvContract.Programs.COLUMN_POSTER_ART_URI, + TvContract.Programs.COLUMN_THUMBNAIL_URI, + TvContract.Programs.COLUMN_CANONICAL_GENRE, + TvContract.Programs.COLUMN_CONTENT_RATING, + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_VIDEO_WIDTH, + TvContract.Programs.COLUMN_VIDEO_HEIGHT }; - // Columns which is deprecated in NYC - private static final String[] PROJECTION_DEPRECATED_IN_NYC = { - TvContract.Programs.COLUMN_SEASON_NUMBER, - TvContract.Programs.COLUMN_EPISODE_NUMBER - }; - - private static final String[] PROJECTION_ADDED_IN_NYC = { - TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_SEASON_TITLE, - TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER + /** + * Use this projection if you want to create {@link Program} object using + * {@link #fromDvrCursor}. + */ + public static final String[] PROJECTION_DVR = { + // Columns must match what is read in Channel.fromDvrCursor() + DvrContract.DvrPrograms._ID }; - public static final String[] PROJECTION = createProjection(); - - private static String[] createProjection() { - return CollectionUtils - .concatAll(PROJECTION_BASE, BuildCompat.isAtLeastN() ? PROJECTION_ADDED_IN_NYC - : PROJECTION_DEPRECATED_IN_NYC); - } - /** * Creates {@code Program} object from cursor. * @@ -90,38 +79,39 @@ public final class Program implements Comparable<Program> { // Columns read must match the order of match {@link #PROJECTION} Builder builder = new Builder(); int index = 0; - builder.setId(cursor.getLong(index++)); builder.setChannelId(cursor.getLong(index++)); builder.setTitle(cursor.getString(index++)); builder.setEpisodeTitle(cursor.getString(index++)); + builder.setSeasonNumber(cursor.getInt(index++)); + builder.setEpisodeNumber(cursor.getInt(index++)); builder.setDescription(cursor.getString(index++)); builder.setPosterArtUri(cursor.getString(index++)); builder.setThumbnailUri(cursor.getString(index++)); builder.setCanonicalGenres(cursor.getString(index++)); - builder.setContentRatings( - TvContentRatingCache.getInstance().getRatings(cursor.getString(index++))); + builder.setContentRatings(TvContentRatingCache.getInstance() + .getRatings(cursor.getString(index++))); builder.setStartTimeUtcMillis(cursor.getLong(index++)); builder.setEndTimeUtcMillis(cursor.getLong(index++)); builder.setVideoWidth((int) cursor.getLong(index++)); builder.setVideoHeight((int) cursor.getLong(index++)); - if (BuildCompat.isAtLeastN()) { - builder.setSeasonNumber(cursor.getString(index++)); - builder.setSeasonTitle(cursor.getString(index++)); - builder.setEpisodeNumber(cursor.getString(index++)); - } else { - builder.setSeasonNumber(cursor.getString(index++)); - builder.setEpisodeNumber(cursor.getString(index++)); - } return builder.build(); } - private long mId; + /** + * Creates a {@link Program} object from the DVR database. + */ + public static Program fromDvrCursor(Cursor c) { + Program program = new Program(); + int index = -1; + program.mDvrId = c.getLong(++index); + return program; + } + private long mChannelId; private String mTitle; private String mEpisodeTitle; - private String mSeasonNumber; - private String mSeasonTitle; - private String mEpisodeNumber; + private int mSeasonNumber; + private int mEpisodeNumber; private long mStartTimeUtcMillis; private long mEndTimeUtcMillis; private String mDescription; @@ -132,6 +122,8 @@ public final class Program implements Comparable<Program> { private int[] mCanonicalGenreIds; private TvContentRating[] mContentRatings; + private long mDvrId; + /** * TODO(DVR): Need to fill the following data. */ @@ -142,10 +134,6 @@ public final class Program implements Comparable<Program> { // Do nothing. } - public long getId() { - return mId; - } - public long getChannelId() { return mChannelId; } @@ -173,22 +161,13 @@ public final class Program implements Comparable<Program> { } public String getEpisodeDisplayTitle(Context context) { - if (!TextUtils.isEmpty(mSeasonNumber) && !TextUtils.isEmpty(mEpisodeNumber) - && !TextUtils.isEmpty(mEpisodeTitle)) { + if (mSeasonNumber > 0 && mEpisodeNumber > 0 && !TextUtils.isEmpty(mEpisodeTitle)) { return String.format(context.getResources().getString(R.string.episode_format), mSeasonNumber, mEpisodeNumber, mEpisodeTitle); } return mEpisodeTitle; } - public String getSeasonNumber() { - return mSeasonNumber; - } - - public String getEpisodeNumber() { - return mEpisodeNumber; - } - public long getStartTimeUtcMillis() { return mStartTimeUtcMillis; } @@ -260,12 +239,19 @@ public final class Program implements Comparable<Program> { return false; } + /** + * Returns an ID in DVR database. + */ + public long getDvrId() { + return mDvrId; + } + @Override public int hashCode() { return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis, mTitle, mEpisodeTitle, mDescription, mVideoWidth, mVideoHeight, mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings), - Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mSeasonTitle, mEpisodeNumber); + Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mEpisodeNumber); } @Override @@ -286,9 +272,8 @@ public final class Program implements Comparable<Program> { && Objects.equals(mThumbnailUri, program.mThumbnailUri) && Arrays.equals(mContentRatings, program.mContentRatings) && Arrays.equals(mCanonicalGenreIds, program.mCanonicalGenreIds) - && Objects.equals(mSeasonNumber, program.mSeasonNumber) - && Objects.equals(mSeasonTitle, program.mSeasonTitle) - && Objects.equals(mEpisodeNumber, program.mEpisodeNumber); + && mSeasonNumber == program.mSeasonNumber + && mEpisodeNumber == program.mEpisodeNumber; } @Override @@ -299,12 +284,11 @@ public final class Program implements Comparable<Program> { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("Program[" + mId + "]{") + builder.append("Program{") .append("channelId=").append(mChannelId) .append(", title=").append(mTitle) .append(", episodeTitle=").append(mEpisodeTitle) .append(", seasonNumber=").append(mSeasonNumber) - .append(", seasonTitle=").append(mSeasonTitle) .append(", episodeNumber=").append(mEpisodeNumber) .append(", startTimeUtcSec=").append(Utils.toTimeString(mStartTimeUtcMillis)) .append(", endTimeUtcSec=").append(Utils.toTimeString(mEndTimeUtcMillis)) @@ -326,12 +310,10 @@ public final class Program implements Comparable<Program> { return; } - mId = other.mId; mChannelId = other.mChannelId; mTitle = other.mTitle; mEpisodeTitle = other.mEpisodeTitle; mSeasonNumber = other.mSeasonNumber; - mSeasonTitle = other.mSeasonTitle; mEpisodeNumber = other.mEpisodeNumber; mStartTimeUtcMillis = other.mStartTimeUtcMillis; mEndTimeUtcMillis = other.mEndTimeUtcMillis; @@ -346,19 +328,17 @@ public final class Program implements Comparable<Program> { public static final class Builder { private final Program mProgram; - private long mId; public Builder() { mProgram = new Program(); // Fill initial data. mProgram.mChannelId = Channel.INVALID_ID; - mProgram.mTitle = null; - mProgram.mSeasonNumber = null; - mProgram.mSeasonTitle = null; - mProgram.mEpisodeNumber = null; + mProgram.mTitle = "title"; + mProgram.mSeasonNumber = -1; + mProgram.mEpisodeNumber = -1; mProgram.mStartTimeUtcMillis = -1; mProgram.mEndTimeUtcMillis = -1; - mProgram.mDescription = null; + mProgram.mDescription = "description"; } public Builder(Program other) { @@ -366,11 +346,6 @@ public final class Program implements Comparable<Program> { mProgram.copyFrom(other); } - public Builder setId(long id) { - mProgram.mId = id; - return this; - } - public Builder setChannelId(long channelId) { mProgram.mChannelId = channelId; return this; @@ -386,17 +361,12 @@ public final class Program implements Comparable<Program> { return this; } - public Builder setSeasonNumber(String seasonNumber) { + public Builder setSeasonNumber(int seasonNumber) { mProgram.mSeasonNumber = seasonNumber; return this; } - public Builder setSeasonTitle(String seasonTitle) { - mProgram.mSeasonTitle = seasonTitle; - return this; - } - - public Builder setEpisodeNumber(String episodeNumber) { + public Builder setEpisodeNumber(int episodeNumber) { mProgram.mEpisodeNumber = episodeNumber; return this; } @@ -503,10 +473,11 @@ public final class Program implements Comparable<Program> { boolean isDuplicate = p1.getChannelId() == p2.getChannelId() && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis() && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis(); - if (DEBUG && BuildConfig.ENG && isDuplicate) { + if (BuildConfig.ENG && isDuplicate) { Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \"" + p2.getTitle() + "\""); } return isDuplicate; } + } diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java index 88db91b9..6c167238 100644 --- a/src/com/android/tv/data/ProgramDataManager.java +++ b/src/com/android/tv/data/ProgramDataManager.java @@ -28,17 +28,16 @@ import android.os.Looper; import android.os.Message; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; -import android.util.ArraySet; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; +import com.android.tv.common.CollectionUtils; import com.android.tv.common.MemoryManageable; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.epg.EpgFetcher; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.Clock; import com.android.tv.util.MultiLongSparseArray; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -91,7 +90,7 @@ public class ProgramDataManager implements MemoryManageable { private final MultiLongSparseArray<OnCurrentProgramUpdatedListener> mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>(); private final Handler mHandler; - private final Set<Listener> mListeners = new ArraySet<>(); + private final Set<Listener> mListeners = CollectionUtils.createSmallSet(); private final ContentObserver mProgramObserver; @@ -109,12 +108,8 @@ public class ProgramDataManager implements MemoryManageable { private boolean mPauseProgramUpdate = false; private final LruCache<Long, Program> mZeroLengthProgramCache = new LruCache<>(10); - // TODO: Change to final. - private EpgFetcher mEpgFetcher; - public ProgramDataManager(Context context) { this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper()); - mEpgFetcher = new EpgFetcher(context); } @VisibleForTesting @@ -133,8 +128,8 @@ public class ProgramDataManager implements MemoryManageable { } if (mPrefetchEnabled) { // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be quite long - // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message - // and send MSG_UPDATE_PREFETCH_PROGRAM again. + // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message and + // send MSG_UPDATE_PREFETCH_PROGRAM again. mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM); mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM); } @@ -174,9 +169,6 @@ public class ProgramDataManager implements MemoryManageable { } mContentResolver.registerContentObserver(Programs.CONTENT_URI, true, mProgramObserver); - if (mEpgFetcher != null) { - mEpgFetcher.start(); - } } /** @@ -190,9 +182,6 @@ public class ProgramDataManager implements MemoryManageable { } mStarted = false; - if (mEpgFetcher != null) { - mEpgFetcher.stop(); - } mContentResolver.unregisterContentObserver(mProgramObserver); mHandler.removeCallbacksAndMessages(null); @@ -212,18 +201,6 @@ public class ProgramDataManager implements MemoryManageable { } /** - * Reloads program data. - */ - public void reload() { - if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) { - mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS); - } - if (mPrefetchEnabled && !mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) { - mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM); - } - } - - /** * A listener interface to receive notification on program data retrieval from DB. */ public interface Listener { @@ -624,22 +601,6 @@ public class ProgramDataManager implements MemoryManageable { } } - /** - * Gets an single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. - */ - public static class QueryProgramTask extends AsyncDbTask.AsyncQueryItemTask<Program> { - - public QueryProgramTask(ContentResolver contentResolver, long programId) { - super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null, - null, null); - } - - @Override - protected Program fromCursor(Cursor c) { - return Program.fromCursor(c); - } - } - private class MyHandler extends Handler { public MyHandler(Looper looper) { super(looper); diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java index df842737..04f8258a 100644 --- a/src/com/android/tv/data/StreamInfo.java +++ b/src/com/android/tv/data/StreamInfo.java @@ -30,7 +30,6 @@ public interface StreamInfo { int getVideoWidth(); int getVideoHeight(); float getVideoFrameRate(); - float getVideoDisplayAspectRatio(); int getVideoDefinitionLevel(); int getAudioChannelCount(); boolean hasClosedCaption(); diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java index fc6672d2..cff8cd5c 100644 --- a/src/com/android/tv/data/WatchedHistoryManager.java +++ b/src/com/android/tv/data/WatchedHistoryManager.java @@ -3,12 +3,10 @@ package com.android.tv.data; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.Log; @@ -44,8 +42,8 @@ public class WatchedHistoryManager { private boolean mStarted; private boolean mLoaded; private SharedPreferences mSharedPreferences; - private final OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener = - new OnSharedPreferenceChangeListener() { + private SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener = + new SharedPreferences.OnSharedPreferenceChangeListener() { @Override @MainThread public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @@ -82,7 +80,7 @@ public class WatchedHistoryManager { private final Context mContext; private Listener mListener; private final int mMaxHistorySize; - private final Handler mHandler; + private Handler mHandler; public WatchedHistoryManager(Context context) { this(context, MAX_HISTORY_SIZE); @@ -199,7 +197,6 @@ public class WatchedHistoryManager { * Returns watched history in the ascending order of time. In other words, the first element * is the oldest and the last element is the latest record. */ - @NonNull public List<WatchedRecord> getWatchedHistory() { return Collections.unmodifiableList(mWatchedHistory); } diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java deleted file mode 100644 index 9ff527d8..00000000 --- a/src/com/android/tv/data/epg/EpgFetcher.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 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.android.tv.data.epg; - -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.media.tv.TvContract.Programs; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager.TvInputCallback; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.util.Log; - -import com.android.tv.Features; -import com.android.tv.TvApplication; -import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; -import com.android.tv.data.Program; -import com.android.tv.util.RecurringRunner; -import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * An utility class to fetch the EPG. This class isn't thread-safe. - */ -public class EpgFetcher { - private static final String TAG = "EpgFetcher"; - private static final boolean DEBUG = false; - - private static final int MSG_FETCH_EPG = 1; - - private static final long EPG_PREFETCH_RECURRING_PERIOD_MS = TimeUnit.HOURS.toMillis(4); - private static final long EPG_READER_INIT_WAIT_MS = TimeUnit.MINUTES.toMillis(1); - private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(30); - - private static final int BATCH_OPERATION_COUNT = 100; - - // Value: Long - private static final String KEY_LAST_UPDATED_EPG_TIMESTAMP = - "com.android.tv.data.epg.EpgFetcher.LastUpdatedEpgTimestamp"; - - private final Context mContext; - private final TvInputManagerHelper mInputHelper; - private final TvInputCallback mInputCallback; - private HandlerThread mHandlerThread; - private EpgFetcherHandler mHandler; - private RecurringRunner mRecurringRunner; - - private long mLastEpgTimestamp = -1; - - public EpgFetcher(Context context) { - mContext = context; - mInputHelper = TvApplication.getSingletons(mContext).getTvInputManagerHelper(); - mInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - if (Utils.isInternalTvInput(mContext, inputId)) { - mHandler.removeMessages(MSG_FETCH_EPG); - mHandler.sendEmptyMessage(MSG_FETCH_EPG); - } - } - }; - } - - /** - * Starts fetching EPG. - */ - public void start() { - if (DEBUG) Log.d(TAG, "Request to start fetching EPG."); - if (!Features.FETCH_EPG.isEnabled(mContext)) { - return; - } - if (mHandlerThread == null) { - mHandlerThread = new HandlerThread("EpgFetcher"); - mHandlerThread.start(); - mHandler = new EpgFetcherHandler(mHandlerThread.getLooper(), this); - mInputHelper.addCallback(mInputCallback); - mRecurringRunner = new RecurringRunner(mContext, EPG_PREFETCH_RECURRING_PERIOD_MS, - new Runnable() { - @Override - public void run() { - mHandler.removeMessages(MSG_FETCH_EPG); - mHandler.sendEmptyMessage(MSG_FETCH_EPG); - } - }, null); - mRecurringRunner.start(); - } - } - - /** - * Stops fetching EPG. - */ - public void stop() { - if (mHandlerThread == null) { - return; - } - mRecurringRunner.stop(); - mHandler.removeCallbacksAndMessages(null); - mHandler = null; - mHandlerThread.quit(); - mHandlerThread = null; - } - - private void onFetchEpg() { - if (DEBUG) Log.d(TAG, "Start fetching EPG."); - // Check for the internal inputs. - boolean hasInternalInput = false; - for (TvInputInfo input : mInputHelper.getTvInputInfos(true, true)) { - if (Utils.isInternalTvInput(mContext, input.getId())) { - hasInternalInput = true; - break; - } - } - if (!hasInternalInput) { - if (DEBUG) Log.d(TAG, "No internal input found."); - return; - } - // Check if EPG reader is available. - EpgReader epgReader = new StubEpgReader(mContext); - if (!epgReader.isAvailable()) { - if (DEBUG) Log.d(TAG, "EPG reader is not temporarily available."); - mHandler.removeMessages(MSG_FETCH_EPG); - mHandler.sendEmptyMessageDelayed(MSG_FETCH_EPG, EPG_READER_INIT_WAIT_MS); - return; - } - // Check the EPG Timestamp. - long epgTimestamp = epgReader.getEpgTimestamp(); - if (epgTimestamp <= getLastUpdatedEpgTimestamp()) { - if (DEBUG) Log.d(TAG, "No new EPG."); - return; - } - - List<Channel> channels = epgReader.getChannels(); - for (Channel channel : channels) { - List<Program> programs = new ArrayList<>(epgReader.getPrograms(channel.getId())); - Collections.sort(programs); - if (DEBUG) { - Log.d(TAG, "Fetching " + programs.size() + " programs for channel " + channel); - } - updateEpg(channel.getId(), programs); - } - - setLastUpdatedEpgTimestamp(epgTimestamp); - } - - private long getLastUpdatedEpgTimestamp() { - if (mLastEpgTimestamp < 0) { - mLastEpgTimestamp = PreferenceManager.getDefaultSharedPreferences(mContext).getLong( - KEY_LAST_UPDATED_EPG_TIMESTAMP, 0); - } - return mLastEpgTimestamp; - } - - private void setLastUpdatedEpgTimestamp(long timestamp) { - mLastEpgTimestamp = timestamp; - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putLong( - KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp); - } - - private void updateEpg(long channelId, List<Program> newPrograms) { - final int fetchedProgramsCount = newPrograms.size(); - if (fetchedProgramsCount == 0) { - return; - } - long startTimeMs = System.currentTimeMillis(); - long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION; - List<Program> oldPrograms = queryPrograms(mContext.getContentResolver(), channelId, - startTimeMs, endTimeMs); - Program currentOldProgram = oldPrograms.size() > 0 ? oldPrograms.get(0) : null; - int oldProgramsIndex = 0; - int newProgramsIndex = 0; - // Skip the past programs. They will be automatically removed by the system. - if (currentOldProgram != null) { - long oldStartTimeUtcMillis = currentOldProgram.getStartTimeUtcMillis(); - for (Program program : newPrograms) { - if (program.getEndTimeUtcMillis() > oldStartTimeUtcMillis) { - break; - } - newProgramsIndex++; - } - } - // Compare the new programs with old programs one by one and update/delete the old one - // or insert new program if there is no matching program in the database. - ArrayList<ContentProviderOperation> ops = new ArrayList<>(); - while (newProgramsIndex < fetchedProgramsCount) { - // TODO: Extract to method and make test. - Program oldProgram = oldProgramsIndex < oldPrograms.size() - ? oldPrograms.get(oldProgramsIndex) : null; - Program newProgram = newPrograms.get(newProgramsIndex); - boolean addNewProgram = false; - if (oldProgram != null) { - if (oldProgram.equals(newProgram)) { - // Exact match. No need to update. Move on to the next programs. - oldProgramsIndex++; - newProgramsIndex++; - } else if (isSameTitleAndOverlap(oldProgram, newProgram)) { - if (!oldProgram.equals(oldProgram)) { - // Partial match. Update the old program with the new one. - // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There - // could be application specific settings which belong to the old program. - ops.add(ContentProviderOperation.newUpdate( - TvContract.buildProgramUri(oldProgram.getId())) - .withValues(toContentValues(newProgram)) - .build()); - } - oldProgramsIndex++; - newProgramsIndex++; - } else if (oldProgram.getEndTimeUtcMillis() - < newProgram.getEndTimeUtcMillis()) { - // No match. Remove the old program first to see if the next program in - // {@code oldPrograms} partially matches the new program. - ops.add(ContentProviderOperation.newDelete( - TvContract.buildProgramUri(oldProgram.getId())) - .build()); - oldProgramsIndex++; - } else { - // No match. The new program does not match any of the old programs. Insert - // it as a new program. - addNewProgram = true; - newProgramsIndex++; - } - } else { - // No old programs. Just insert new programs. - addNewProgram = true; - newProgramsIndex++; - } - if (addNewProgram) { - ops.add(ContentProviderOperation - .newInsert(TvContract.Programs.CONTENT_URI) - .withValues(toContentValues(newProgram)) - .build()); - } - // Throttle the batch operation not to cause TransactionTooLargeException. - if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) { - try { - if (DEBUG) { - int size = ops.size(); - Log.d(TAG, "Running " + size + " operations for channel " + channelId); - for (int i = 0; i < size; ++i) { - Log.d(TAG, "Operation(" + i + "): " + ops.get(i)); - } - } - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Failed to insert programs.", e); - return; - } - ops.clear(); - } - } - if (DEBUG) { - Log.d(TAG, "Fetched " + fetchedProgramsCount + " programs for channel " + channelId); - } - } - - private List<Program> queryPrograms(ContentResolver contentResolver, long channelId, - long startTimeMs, long endTimeMs) { - try (Cursor c = mContext.getContentResolver().query( - TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs), - Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) { - if (c == null) { - return Collections.EMPTY_LIST; - } - ArrayList<Program> programs = new ArrayList<>(); - while (c.moveToNext()) { - programs.add(Program.fromCursor(c)); - } - return programs; - } - } - - /** - * Returns {@code true} if the {@code oldProgram} program needs to be updated with the - * {@code newProgram} program. - */ - private boolean isSameTitleAndOverlap(Program oldProgram, Program newProgram) { - // NOTE: Here, we update the old program if it has the same title and overlaps with the - // new program. The test logic is just an example and you can modify this. E.g. check - // whether the both programs have the same program ID if your EPG supports any ID for - // the programs. - return Objects.equals(oldProgram.getTitle(), newProgram.getTitle()) - && oldProgram.getStartTimeUtcMillis() <= newProgram.getEndTimeUtcMillis() - && newProgram.getStartTimeUtcMillis() <= oldProgram.getEndTimeUtcMillis(); - } - - private static ContentValues toContentValues(Program program) { - ContentValues values = new ContentValues(); - values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId()); - putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle()); - putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber()); - putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription()); - putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri()); - values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - program.getStartTimeUtcMillis()); - values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis()); - return values; - } - - private static void putValue(ContentValues contentValues, String key, String value) { - if (TextUtils.isEmpty(value)) { - contentValues.putNull(key); - } else { - contentValues.put(key, value); - } - } - - private static class EpgFetcherHandler extends WeakHandler<EpgFetcher> { - public EpgFetcherHandler (@NonNull Looper looper, EpgFetcher ref) { - super(looper, ref); - } - - @Override - public void handleMessage(Message msg, @NonNull EpgFetcher epgFetcher) { - switch (msg.what) { - case MSG_FETCH_EPG: - epgFetcher.onFetchEpg(); - break; - default: - super.handleMessage(msg); - break; - } - } - } -} diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java deleted file mode 100644 index 1c7712f4..00000000 --- a/src/com/android/tv/data/epg/EpgReader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 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.android.tv.data.epg; - -import android.support.annotation.WorkerThread; - -import com.android.tv.data.Channel; -import com.android.tv.data.Program; - -import java.util.List; - -/** - * An interface used to retrieve the EPG data. This class should be used in worker thread. - */ -@WorkerThread -public interface EpgReader { - /** - * Checks if the reader is available. - */ - boolean isAvailable(); - - /** - * Returns the timestamp of the current EPG. - * The format should be YYYYMMDDHHmmSS as a long value. ex) 20160308141500 - */ - long getEpgTimestamp(); - - /** - * Returns the channels list. - */ - List<Channel> getChannels(); - - /** - * Returns the programs for the given channel. The result is sorted by the start time. - * Note that the {@code Program} doesn't have valid program ID because it's not retrieved from - * TvProvider. - */ - List<Program> getPrograms(long channelId); -} diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java deleted file mode 100644 index 2896e8e5..00000000 --- a/src/com/android/tv/data/epg/StubEpgReader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 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.android.tv.data.epg; - -import android.content.Context; - -import com.android.tv.data.Channel; -import com.android.tv.data.Program; - -import java.util.Collections; -import java.util.List; - -/** - * A stub class to read EPG. - */ -public class StubEpgReader implements EpgReader{ - public StubEpgReader(Context context) { - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public long getEpgTimestamp() { - return 0; - } - - @Override - public List<Channel> getChannels() { - return Collections.EMPTY_LIST; - } - - @Override - public List<Program> getPrograms(long channelId) { - return Collections.EMPTY_LIST; - } -} diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java index d16202a1..eb84aaf9 100644 --- a/src/com/android/tv/dialog/FullscreenDialogFragment.java +++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java @@ -48,6 +48,7 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment { return f; } + private int mViewLayoutResId; private String mTrackerLabel; private DialogView mDialogView; @@ -57,9 +58,9 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment { new FullscreenDialog(getActivity(), R.style.Theme_TV_dialog_Fullscreen); LayoutInflater inflater = LayoutInflater.from(getActivity()); Bundle args = getArguments(); + mViewLayoutResId = args.getInt(VIEW_LAYOUT_ID); mTrackerLabel = args.getString(TRACKER_LABEL); - int viewLayoutResId = args.getInt(VIEW_LAYOUT_ID); - View v = inflater.inflate(viewLayoutResId, null); + View v = inflater.inflate(mViewLayoutResId, null); dialog.setContentView(v); mDialogView = (DialogView) v; mDialogView.initialize((MainActivity) getActivity(), dialog); diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index 0fb469be..a98b5fa0 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -16,155 +16,68 @@ package com.android.tv.dvr; -import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; import android.support.annotation.MainThread; -import android.util.ArraySet; import android.util.Log; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.CollectionUtils; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.recording.RecordedProgram; -import com.android.tv.util.Clock; +import com.android.tv.util.SoftPreconditions; -import java.util.ArrayList; -import java.util.List; import java.util.Set; /** * Base implementation of @{link DataManagerInternal}. */ @MainThread -@TargetApi(Build.VERSION_CODES.N) public abstract class BaseDvrDataManager implements WritableDvrDataManager { private final static String TAG = "BaseDvrDataManager"; private final static boolean DEBUG = false; - protected final Clock mClock; - private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>(); - private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>(); + private final Set<DvrDataManager.Listener> mListeners = CollectionUtils.createSmallSet(); - BaseDvrDataManager(Context context, Clock clock) { + BaseDvrDataManager (Context context){ SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); - mClock = clock; } @Override - public final void addScheduledRecordingListener(ScheduledRecordingListener listener) { - mScheduledRecordingListeners.add(listener); + public final void addListener(DvrDataManager.Listener listener) { + mListeners.add(listener); } @Override - public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) { - mScheduledRecordingListeners.remove(listener); - } - - @Override - public final void addRecordedProgramListener(RecordedProgramListener listener) { - mRecordedProgramListeners.add(listener); - } - - @Override - public final void removeRecordedProgramListener(RecordedProgramListener listener) { - mRecordedProgramListeners.remove(listener); + public final void removeListener(DvrDataManager.Listener listener) { + mListeners.remove(listener); } /** - * Calls {@link RecordedProgramListener#onRecordedProgramAdded(RecordedProgram)} - * for each listener. + * Calls {@link DvrDataManager.Listener#onRecordingAdded(Recording)} for each current listener. */ - protected final void notifyRecordedProgramAdded(RecordedProgram recordedProgram) { - for (RecordedProgramListener l : mRecordedProgramListeners) { - if (DEBUG) Log.d(TAG, "notify " + l + "added " + recordedProgram); - l.onRecordedProgramAdded(recordedProgram); + protected final void notifyRecordingAdded(Recording recording) { + for (Listener l : mListeners) { + if (DEBUG) Log.d(TAG, "notify " + l + "added recording " + recording); + l.onRecordingAdded(recording); } } /** - * Calls {@link RecordedProgramListener#onRecordedProgramChanged(RecordedProgram)} - * for each listener. + * Calls {@link DvrDataManager.Listener#onRecordingRemoved(Recording)} for each current listener. */ - protected final void notifyRecordedProgramChanged(RecordedProgram recordedProgram) { - for (RecordedProgramListener l : mRecordedProgramListeners) { - if (DEBUG) Log.d(TAG, "notify " + l + "changed " + recordedProgram); - l.onRecordedProgramChanged(recordedProgram); + protected final void notifyRecordingRemoved(Recording recording) { + for (Listener l : mListeners) { + if (DEBUG) Log.d(TAG, "notify " + l + "removed recording " + recording); + l.onRecordingRemoved(recording); } } /** - * Calls {@link RecordedProgramListener#onRecordedProgramRemoved(RecordedProgram)} - * for each listener. + * Calls {@link DvrDataManager.Listener#onRecordingStatusChanged(Recording)} for each current + * listener. */ - protected final void notifyRecordedProgramRemoved(RecordedProgram recordedProgram) { - for (RecordedProgramListener l : mRecordedProgramListeners) { - if (DEBUG) Log.d(TAG, "notify " + l + "removed " + recordedProgram); - l.onRecordedProgramRemoved(recordedProgram); + protected final void notifyRecordingStatusChanged(Recording recording) { + for (Listener l : mListeners) { + if (DEBUG) Log.d(TAG, "notify " + l + "changed recording " + recording); + l.onRecordingStatusChanged(recording); } } - - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded(ScheduledRecording)} - * for each listener. - */ - protected final void notifyScheduledRecordingAdded(ScheduledRecording scheduledRecording) { - for (ScheduledRecordingListener l : mScheduledRecordingListeners) { - if (DEBUG) Log.d(TAG, "notify " + l + "added " + scheduledRecording); - l.onScheduledRecordingAdded(scheduledRecording); - } - } - - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved(ScheduledRecording)} - * for each listener. - */ - protected final void notifyScheduledRecordingRemoved(ScheduledRecording scheduledRecording) { - for (ScheduledRecordingListener l : mScheduledRecordingListeners) { - if (DEBUG) { - Log.d(TAG, "notify " + l + "removed " + scheduledRecording); - } - l.onScheduledRecordingRemoved(scheduledRecording); - } - } - - /** - * Calls - * {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged(ScheduledRecording)} - * for each listener. - */ - protected final void notifyScheduledRecordingStatusChanged( - ScheduledRecording scheduledRecording) { - for (ScheduledRecordingListener l : mScheduledRecordingListeners) { - if (DEBUG) Log.d(TAG, "notify " + l + "changed " + scheduledRecording); - l.onScheduledRecordingStatusChanged(scheduledRecording); - } - } - - /** - * Returns a new list with only {@link ScheduledRecording} with a {@link - * ScheduledRecording#getEndTimeMs() endTime} after now. - */ - private List<ScheduledRecording> filterEndTimeIsPast(List<ScheduledRecording> originals) { - List<ScheduledRecording> results = new ArrayList<>(originals.size()); - for (ScheduledRecording r : originals) { - if (r.getEndTimeMs() > mClock.currentTimeMillis()) { - results.add(r); - } - } - return results; - } - - @Override - public List<ScheduledRecording> getStartedRecordings() { - return filterEndTimeIsPast( - getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)); - } - - @Override - public List<ScheduledRecording> getNonStartedScheduledRecordings() { - return filterEndTimeIsPast( - getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)); - } - - protected abstract List<ScheduledRecording> getRecordingsWithState(int state); } diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java index c96104e5..4f8b0525 100644 --- a/src/com/android/tv/dvr/DvrDataManager.java +++ b/src/com/android/tv/dvr/DvrDataManager.java @@ -20,8 +20,6 @@ import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.util.Range; -import com.android.tv.common.recording.RecordedProgram; - import java.util.List; /** @@ -34,24 +32,24 @@ public interface DvrDataManager { boolean isInitialized(); /** - * Returns past recordings. + * Returns recordings. */ - List<RecordedProgram> getRecordedPrograms(); + List<Recording> getRecordings(); /** - * Returns all {@link ScheduledRecording} regardless of state. + * Returns past recordings. */ - List<ScheduledRecording> getAllScheduledRecordings(); + List<Recording> getFinishedRecordings(); /** - * Returns started recordings that expired. + * Returns started recordings. */ - List<ScheduledRecording> getStartedRecordings(); + List<Recording> getStartedRecordings(); /** - * Returns scheduled but not started recordings that have not expired. + * Returns scheduled recordings */ - List<ScheduledRecording> getNonStartedScheduledRecordings(); + List<Recording> getScheduledRecordings(); /** * Returns season recordings. @@ -75,60 +73,27 @@ public interface DvrDataManager { * * @param period a time period in milliseconds. */ - List<ScheduledRecording> getRecordingsThatOverlapWith(Range<Long> period); - - /** - * Add a {@link ScheduledRecordingListener}. - */ - void addScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener); - - /** - * Remove a {@link ScheduledRecordingListener}. - */ - void removeScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener); - - /** - * Add a {@link RecordedProgramListener}. - */ - void addRecordedProgramListener(RecordedProgramListener listener); + List<Recording> getRecordingsThatOverlapWith(Range<Long> period); /** - * Remove a {@link RecordedProgramListener}. + * Add a {@link Listener}. */ - void removeRecordedProgramListener(RecordedProgramListener listener); + void addListener(Listener listener); /** - * Returns the scheduled recording program with the given recordingId or null if is not found. + * Remove a {@link Listener}. */ - @Nullable - ScheduledRecording getScheduledRecording(long recordingId); - - - /** - * Returns the scheduled recording program with the given programId or null if is not found. - */ - @Nullable - ScheduledRecording getScheduledRecordingForProgramId(long programId); + void removeListener(Listener listener); /** - * Returns the recorded program with the given recordingId or null if is not found. + * Returns the recording with the given recordingId or null if is not found */ @Nullable - RecordedProgram getRecordedProgram(long recordingId); - - interface ScheduledRecordingListener { - void onScheduledRecordingAdded(ScheduledRecording scheduledRecording); - - void onScheduledRecordingRemoved(ScheduledRecording scheduledRecording); - - void onScheduledRecordingStatusChanged(ScheduledRecording scheduledRecording); - } - - interface RecordedProgramListener { - void onRecordedProgramAdded(RecordedProgram recordedProgram); - - void onRecordedProgramChanged(RecordedProgram recordedProgram); + Recording getRecording(long recordingId); - void onRecordedProgramRemoved(RecordedProgram recordedProgram); + interface Listener { + void onRecordingAdded(Recording recording); + void onRecordingRemoved(Recording recording); + void onRecordingStatusChanged(Recording recording); } } diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index 02c47750..647d9bd7 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -16,174 +16,95 @@ package com.android.tv.dvr; -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.ContentUris; import android.content.Context; -import android.database.ContentObserver; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import android.util.ArraySet; import android.util.Log; import android.util.Range; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.recording.RecordedProgram; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.Recording.RecordingState; import com.android.tv.dvr.provider.AsyncDvrDbTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryTask; -import com.android.tv.util.AsyncDbTask; -import com.android.tv.util.Clock; +import com.android.tv.util.SoftPreconditions; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Set; /** * DVR Data manager to handle recordings and schedules. */ @MainThread -@TargetApi(Build.VERSION_CODES.N) public class DvrDataManagerImpl extends BaseDvrDataManager { private static final String TAG = "DvrDataManagerImpl"; - private static final boolean DEBUG = false; - private final HashMap<Long, ScheduledRecording> mScheduledRecordings = new HashMap<>(); - private final HashMap<Long, ScheduledRecording> mProgramId2ScheduledRecordings = - new HashMap<>(); - private final HashMap<Long, RecordedProgram> mRecordedPrograms = new HashMap<>(); + private Context mContext; + private boolean mLoadFinished; + private final HashMap<Long, Recording> mRecordings = new HashMap<>(); + private AsyncDvrQueryTask mQueryTask; - private final Context mContext; - private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - private final ContentObserver mContentObserver = new ContentObserver(mMainThreadHandler) { - - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - @Override - public void onChange(boolean selfChange, @Nullable final Uri uri) { - if (uri == null) { - // TODO reload everything. - } - AsyncRecordedProgramQueryTask task = new AsyncRecordedProgramQueryTask( - mContext.getContentResolver(), uri); - task.executeOnDbThread(); - mPendingTasks.add(task); - } - }; - - private void onObservedChange(Uri uri, RecordedProgram recordedProgram) { - long id = ContentUris.parseId(uri); - if (DEBUG) { - Log.d(TAG, "changed recorded program #" + id + " to " + recordedProgram); - } - if (recordedProgram == null) { - RecordedProgram old = mRecordedPrograms.remove(id); - if (old != null) { - notifyRecordedProgramRemoved(old); - } else { - Log.w(TAG, "Could not find old version of deleted program #" + id); - } - } else { - RecordedProgram old = mRecordedPrograms.put(id, recordedProgram); - if (old == null) { - notifyRecordedProgramAdded(recordedProgram); - } else { - notifyRecordedProgramChanged(recordedProgram); - } - } - } - - private boolean mDvrLoadFinished; - private boolean mRecordedProgramLoadFinished; - private final Set<AsyncTask> mPendingTasks = new ArraySet<>(); - - public DvrDataManagerImpl(Context context, Clock clock) { - super(context, clock); + public DvrDataManagerImpl(Context context) { + super(context); mContext = context; } public void start() { - AsyncDvrQueryTask mDvrQueryTask = new AsyncDvrQueryTask(mContext) { - + mQueryTask = new AsyncDvrQueryTask(mContext) { @Override - protected void onCancelled(List<ScheduledRecording> scheduledRecordings) { - mPendingTasks.remove(this); - } - - @Override - protected void onPostExecute(List<ScheduledRecording> result) { - mPendingTasks.remove(this); - mDvrLoadFinished = true; - for (ScheduledRecording r : result) { - mScheduledRecordings.put(r.getId(), r); + protected void onPostExecute(List<Recording> result) { + mQueryTask = null; + mLoadFinished = true; + for (Recording r : result) { + mRecordings.put(r.getId(), r); } } }; - mDvrQueryTask.executeOnDbThread(); - mPendingTasks.add(mDvrQueryTask); - AsyncRecordedProgramsQueryTask mRecordedProgramQueryTask = - new AsyncRecordedProgramsQueryTask(mContext.getContentResolver()); - mRecordedProgramQueryTask.executeOnDbThread(); - ContentResolver cr = mContext.getContentResolver(); - cr.registerContentObserver(TvContract.RecordedPrograms.CONTENT_URI, true, mContentObserver); + mQueryTask.executeOnDbThread(); } public void stop() { - ContentResolver cr = mContext.getContentResolver(); - cr.unregisterContentObserver(mContentObserver); - Iterator<AsyncTask> i = mPendingTasks.iterator(); - while (i.hasNext()) { - AsyncTask task = i.next(); - i.remove(); - task.cancel(true); + if (mQueryTask != null) { + mQueryTask.cancel(true); + mQueryTask = null; } } @Override public boolean isInitialized() { - return mDvrLoadFinished && mRecordedProgramLoadFinished; + return mLoadFinished; } - private List<ScheduledRecording> getScheduledRecordingsPrograms() { - if (!mDvrLoadFinished) { + @Override + public List<Recording> getRecordings() { + if (!mLoadFinished) { return Collections.emptyList(); } - ArrayList<ScheduledRecording> list = new ArrayList<>(mScheduledRecordings.size()); - list.addAll(mScheduledRecordings.values()); - Collections.sort(list, ScheduledRecording.START_TIME_COMPARATOR); - return list; + ArrayList<Recording> list = new ArrayList<>(mRecordings.size()); + list.addAll(mRecordings.values()); + Collections.sort(list, Recording.START_TIME_COMPARATOR); + return Collections.unmodifiableList(list); } @Override - public List<RecordedProgram> getRecordedPrograms() { - if (!mRecordedProgramLoadFinished) { - return Collections.emptyList(); - } - return new ArrayList<>(mRecordedPrograms.values()); + public List<Recording> getFinishedRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_FINISHED); + } + + @Override + public List<Recording> getStartedRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_IN_PROGRESS); } @Override - public List<ScheduledRecording> getAllScheduledRecordings() { - return new ArrayList<>(mScheduledRecordings.values()); + public List<Recording> getScheduledRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_NOT_STARTED); } - protected List<ScheduledRecording> getRecordingsWithState(@RecordingState int state) { - List<ScheduledRecording> result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { + private List<Recording> getRecordingsWithState(@RecordingState int state) { + List<Recording> result = new ArrayList<>(); + for (Recording r : mRecordings.values()) { if (r.getState() == state) { result.add(r); } @@ -199,29 +120,29 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { @Override public long getNextScheduledStartTimeAfter(long startTime) { - return getNextStartTimeAfter(getScheduledRecordingsPrograms(), startTime); + return getNextStartTimeAfter(getRecordings(), startTime); } @VisibleForTesting - static long getNextStartTimeAfter(List<ScheduledRecording> scheduledRecordings, long startTime) { + static long getNextStartTimeAfter(List<Recording> recordings, long startTime) { int start = 0; - int end = scheduledRecordings.size() - 1; + int end = recordings.size() - 1; while (start <= end) { int mid = (start + end) / 2; - if (scheduledRecordings.get(mid).getStartTimeMs() <= startTime) { + if (recordings.get(mid).getStartTimeMs() <= startTime) { start = mid + 1; } else { end = mid - 1; } } - return start < scheduledRecordings.size() ? scheduledRecordings.get(start).getStartTimeMs() + return start < recordings.size() ? recordings.get(start).getStartTimeMs() : NEXT_START_TIME_NOT_FOUND; } @Override - public List<ScheduledRecording> getRecordingsThatOverlapWith(Range<Long> period) { - List<ScheduledRecording> result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { + public List<Recording> getRecordingsThatOverlapWith(Range<Long> period) { + List<Recording> result = new ArrayList<>(); + for (Recording r : mRecordings.values()) { if (r.isOverLapping(period)) { result.add(r); } @@ -231,56 +152,38 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { @Nullable @Override - public ScheduledRecording getScheduledRecording(long recordingId) { - if (mDvrLoadFinished) { - return mScheduledRecordings.get(recordingId); - } - return null; - } - - @Nullable - @Override - public ScheduledRecording getScheduledRecordingForProgramId(long programId) { - if (mDvrLoadFinished) { - return mProgramId2ScheduledRecordings.get(programId); + public Recording getRecording(long recordingId) { + if (mLoadFinished) { + return mRecordings.get(recordingId); } return null; } - @Nullable - @Override - public RecordedProgram getRecordedProgram(long recordingId) { - return mRecordedPrograms.get(recordingId); - } - @Override - public void addScheduledRecording(final ScheduledRecording scheduledRecording) { + public void addRecording(final Recording recording) { new AsyncDvrDbTask.AsyncAddRecordingTask(mContext) { @Override - protected void onPostExecute(List<ScheduledRecording> scheduledRecordings) { - super.onPostExecute(scheduledRecordings); - SoftPreconditions.checkArgument(scheduledRecordings.size() == 1); - for (ScheduledRecording r : scheduledRecordings) { + protected void onPostExecute(List<Recording> recordings) { + super.onPostExecute(recordings); + SoftPreconditions.checkArgument(recordings.size() == 1); + for (Recording r : recordings) { if (r.getId() != -1) { - mScheduledRecordings.put(r.getId(), r); - if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) { - mProgramId2ScheduledRecordings.put(r.getProgramId(), r); - } - notifyScheduledRecordingAdded(r); + mRecordings.put(r.getId(), r); + notifyRecordingAdded(r); } else { Log.w(TAG, "Error adding " + r); } } } - }.executeOnDbThread(scheduledRecording); + }.executeOnDbThread(recording); } @Override public void addSeasonRecording(SeasonRecording seasonRecording) { } @Override - public void removeScheduledRecording(final ScheduledRecording scheduledRecording) { + public void removeRecording(final Recording recording) { new AsyncDvrDbTask.AsyncDeleteRecordingTask(mContext) { @Override protected void onPostExecute(List<Integer> counts) { @@ -288,27 +191,23 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { SoftPreconditions.checkArgument(counts.size() == 1); for (Integer c : counts) { if (c == 1) { - mScheduledRecordings.remove(scheduledRecording.getId()); - if (scheduledRecording.getProgramId() != ScheduledRecording.ID_NOT_SET) { - mProgramId2ScheduledRecordings - .remove(scheduledRecording.getProgramId()); - } + mRecordings.remove(recording.getId()); //TODO change to notifyRecordingUpdated - notifyScheduledRecordingRemoved(scheduledRecording); + notifyRecordingRemoved(recording); } else { - Log.w(TAG, "Error removing " + scheduledRecording); + Log.w(TAG, "Error removing " + recording); } } } - }.executeOnDbThread(scheduledRecording); + }.executeOnDbThread(recording); } @Override public void removeSeasonSchedule(SeasonRecording seasonSchedule) { } @Override - public void updateScheduledRecording(final ScheduledRecording scheduledRecording) { + public void updateRecording(final Recording recording) { new AsyncDvrDbTask.AsyncUpdateRecordingTask(mContext) { @Override protected void onPostExecute(List<Integer> counts) { @@ -316,88 +215,15 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { SoftPreconditions.checkArgument(counts.size() == 1); for (Integer c : counts) { if (c == 1) { - ScheduledRecording oldScheduledRecording = mScheduledRecordings - .put(scheduledRecording.getId(), scheduledRecording); - long programId = scheduledRecording.getProgramId(); - if (oldScheduledRecording != null - && oldScheduledRecording.getProgramId() != programId - && oldScheduledRecording.getProgramId() - != ScheduledRecording.ID_NOT_SET) { - ScheduledRecording oldValueForProgramId = mProgramId2ScheduledRecordings - .get(oldScheduledRecording.getProgramId()); - if (oldValueForProgramId.getId() == scheduledRecording.getId()) { - //Only remove the old ScheduledRecording if it has the same ID as - // the new one. - mProgramId2ScheduledRecordings - .remove(oldScheduledRecording.getProgramId()); - } - } - if (programId != ScheduledRecording.ID_NOT_SET) { - mProgramId2ScheduledRecordings.put(programId, scheduledRecording); - } + mRecordings.put(recording.getId(), recording); //TODO change to notifyRecordingUpdated - notifyScheduledRecordingStatusChanged(scheduledRecording); + notifyRecordingStatusChanged(recording); } else { - Log.w(TAG, "Error updating " + scheduledRecording); + Log.w(TAG, "Error updating " + recording); } } - } - }.executeOnDbThread(scheduledRecording); - } - - private final class AsyncRecordedProgramsQueryTask - extends AsyncDbTask.AsyncQueryListTask<RecordedProgram> { - public AsyncRecordedProgramsQueryTask(ContentResolver contentResolver) { - super(contentResolver, TvContract.RecordedPrograms.CONTENT_URI, - RecordedProgram.PROJECTION, null, null, null); - } - - @Override - protected RecordedProgram fromCursor(Cursor c) { - return RecordedProgram.fromCursor(c); - } - - @Override - protected void onCancelled(List<RecordedProgram> scheduledRecordings) { - mPendingTasks.remove(this); - } - @Override - protected void onPostExecute(List<RecordedProgram> result) { - mPendingTasks.remove(this); - mRecordedProgramLoadFinished = true; - if (result != null) { - for (RecordedProgram r : result) { - mRecordedPrograms.put(r.getId(), r); - } } - } - } - - private final class AsyncRecordedProgramQueryTask - extends AsyncDbTask.AsyncQueryItemTask<RecordedProgram> { - - private final Uri mUri; - - public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) { - super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null); - mUri = uri; - } - - @Override - protected RecordedProgram fromCursor(Cursor c) { - return RecordedProgram.fromCursor(c); - } - - @Override - protected void onCancelled(RecordedProgram recordedProgram) { - mPendingTasks.remove(this); - } - - @Override - protected void onPostExecute(RecordedProgram recordedProgram) { - mPendingTasks.remove(this); - onObservedChange(mUri, recordedProgram); - } + }.executeOnDbThread(recording); } } diff --git a/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java b/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java index 95b342bb..8a19cb29 100644 --- a/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java @@ -23,9 +23,7 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Range; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.recording.RecordedProgram; -import com.android.tv.util.Clock; +import com.android.tv.util.SoftPreconditions; import java.util.ArrayList; import java.util.Collections; @@ -42,12 +40,11 @@ import java.util.concurrent.atomic.AtomicLong; public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { private final static String TAG = "DvrDataManagerInMemory"; private final AtomicLong mNextId = new AtomicLong(1); - private final Map<Long, ScheduledRecording> mScheduledRecordings = new HashMap<>(); - private final Map<Long, RecordedProgram> mRecordedPrograms = new HashMap<>(); - private final List<SeasonRecording> mSeasonSchedule = new ArrayList<>(); + private final Map<Long, Recording> mRecordings = new HashMap<>(); + private List<SeasonRecording> mSeasonSchedule = new ArrayList<>(); - public DvrDataManagerInMemoryImpl(Context context, Clock clock) { - super(context, clock); + public DvrDataManagerInMemoryImpl(Context context) { + super(context); } @Override @@ -55,20 +52,27 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { return true; } - private List<ScheduledRecording> getScheduledRecordingsPrograms() { - return new ArrayList(mScheduledRecordings.values()); + @Override + public List<Recording> getRecordings() { + return new ArrayList(mRecordings.values()); + } + + @Override + public List<Recording> getFinishedRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_FINISHED); } @Override - public List<RecordedProgram> getRecordedPrograms() { - return new ArrayList<>(mRecordedPrograms.values()); + public List<Recording> getStartedRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_IN_PROGRESS); } @Override - public List<ScheduledRecording> getAllScheduledRecordings() { - return new ArrayList<>(mScheduledRecordings.values()); + public List<Recording> getScheduledRecordings() { + return getRecordingsWithState(Recording.STATE_RECORDING_NOT_STARTED); } + @Override public List<SeasonRecording> getSeasonRecordings() { return mSeasonSchedule; } @@ -76,9 +80,9 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { @Override public long getNextScheduledStartTimeAfter(long startTime) { - List<ScheduledRecording> temp = getNonStartedScheduledRecordings(); - Collections.sort(temp, ScheduledRecording.START_TIME_COMPARATOR); - for (ScheduledRecording r : temp) { + List<Recording> temp = getScheduledRecordings(); + Collections.sort(temp, Recording.START_TIME_COMPARATOR); + for (Recording r : temp) { if (r.getStartTimeMs() > startTime) { return r.getStartTimeMs(); } @@ -87,10 +91,10 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } @Override - public List<ScheduledRecording> getRecordingsThatOverlapWith(Range<Long> period) { - List<ScheduledRecording> temp = getScheduledRecordingsPrograms(); - List<ScheduledRecording> result = new ArrayList<>(); - for (ScheduledRecording r : temp) { + public List<Recording> getRecordingsThatOverlapWith(Range<Long> period) { + List<Recording> temp = getRecordings(); + List<Recording> result = new ArrayList<>(); + for (Recording r : temp) { if (r.isOverLapping(period)) { result.add(r); } @@ -99,56 +103,20 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } /** - * Add a new scheduled recording. + * Add a new recording. */ @Override - public void addScheduledRecording(ScheduledRecording scheduledRecording) { - addScheduledRecordingInternal(scheduledRecording); - } - - - public void addRecordedProgram(RecordedProgram recordedProgram) { - addRecordedProgramInternal(recordedProgram); - } - - public void updateRecordedProgram(RecordedProgram r) { - long id = r.getId(); - if (mRecordedPrograms.containsKey(id)) { - mRecordedPrograms.put(id, r); - notifyRecordedProgramChanged(r); - } else { - throw new IllegalArgumentException("Recording not found:" + r); - } - } - - public void removeRecordedProgram(RecordedProgram scheduledRecording) { - mRecordedPrograms.remove(scheduledRecording.getId()); - notifyRecordedProgramRemoved(scheduledRecording); - } - - - public ScheduledRecording addScheduledRecordingInternal(ScheduledRecording scheduledRecording) { - SoftPreconditions - .checkState(scheduledRecording.getId() == ScheduledRecording.ID_NOT_SET, TAG, - "expected id of " + ScheduledRecording.ID_NOT_SET + " but was " - + scheduledRecording); - scheduledRecording = ScheduledRecording.buildFrom(scheduledRecording) - .setId(mNextId.incrementAndGet()) - .build(); - mScheduledRecordings.put(scheduledRecording.getId(), scheduledRecording); - notifyScheduledRecordingAdded(scheduledRecording); - return scheduledRecording; + public void addRecording(Recording recording) { + addRecordingInternal(recording); } - public RecordedProgram addRecordedProgramInternal(RecordedProgram recordedProgram) { - SoftPreconditions.checkState(recordedProgram.getId() == RecordedProgram.ID_NOT_SET, TAG, - "expected id of " + RecordedProgram.ID_NOT_SET + " but was " + recordedProgram); - recordedProgram = RecordedProgram.buildFrom(recordedProgram) - .setId(mNextId.incrementAndGet()) - .build(); - mRecordedPrograms.put(recordedProgram.getId(), recordedProgram); - notifyRecordedProgramAdded(recordedProgram); - return recordedProgram; + public Recording addRecordingInternal(Recording recording) { + SoftPreconditions.checkState(recording.getId() == Recording.ID_NOT_SET, TAG, + "expected id of " + Recording.ID_NOT_SET + " but was " + recording); + recording = Recording.buildFrom(recording).setId(mNextId.incrementAndGet()).build(); + mRecordings.put(recording.getId(), recording); + notifyRecordingAdded(recording); + return recording; } @Override @@ -157,9 +125,9 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } @Override - public void removeScheduledRecording(ScheduledRecording scheduledRecording) { - mScheduledRecordings.remove(scheduledRecording.getId()); - notifyScheduledRecordingRemoved(scheduledRecording); + public void removeRecording(Recording recording) { + mRecordings.remove(recording.getId()); + notifyRecordingRemoved(recording); } @Override @@ -168,11 +136,11 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } @Override - public void updateScheduledRecording(ScheduledRecording r) { + public void updateRecording(Recording r) { long id = r.getId(); - if (mScheduledRecordings.containsKey(id)) { - mScheduledRecordings.put(id, r); - notifyScheduledRecordingStatusChanged(r); + if (mRecordings.containsKey(id)) { + mRecordings.put(id, r); + notifyRecordingStatusChanged(r); } else { throw new IllegalArgumentException("Recording not found:" + r); } @@ -180,32 +148,14 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { @Nullable @Override - public ScheduledRecording getScheduledRecording(long id) { - return mScheduledRecordings.get(id); + public Recording getRecording(long id) { + return mRecordings.get(id); } - @Nullable - @Override - public ScheduledRecording getScheduledRecordingForProgramId(long programId) { - for (ScheduledRecording r : mScheduledRecordings.values()) { - if (r.getProgramId() == programId) { - return r; - } - } - return null; - } - - @Nullable - @Override - public RecordedProgram getRecordedProgram(long recordingId) { - return mRecordedPrograms.get(recordingId); - } - - @Override @NonNull - protected List<ScheduledRecording> getRecordingsWithState(int state) { - ArrayList<ScheduledRecording> result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { + private List<Recording> getRecordingsWithState(int state) { + ArrayList<Recording> result = new ArrayList<>(); + for (Recording r : mRecordings.values()) { if(r.getState() == state){ result.add(r); } diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java index e3dc622e..c62c564b 100644 --- a/src/com/android/tv/dvr/DvrManager.java +++ b/src/com/android/tv/dvr/DvrManager.java @@ -16,33 +16,24 @@ package com.android.tv.dvr; -import android.content.ContentResolver; import android.content.Context; -import android.media.tv.TvInputInfo; -import android.os.Handler; import android.support.annotation.MainThread; import android.support.annotation.NonNull; -import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; -import android.widget.Toast; import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.recording.RecordedProgram; +import com.android.tv.common.recording.RecordingCapability; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; -import com.android.tv.util.AsyncDbTask; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.Utils; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; /** * DVR manager class to add and remove recordings. UI can modify recording list through this class, @@ -54,15 +45,11 @@ public class DvrManager { private final WritableDvrDataManager mDataManager; private final ChannelDataManager mChannelDataManager; private final DvrSessionManager mDvrSessionManager; - // @GuardedBy("mListener") - private final Map<Listener, Handler> mListener = new HashMap<>(); - private final Context mAppContext; public DvrManager(Context context) { SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); ApplicationSingletons appSingletons = TvApplication.getSingletons(context); mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); - mAppContext = context.getApplicationContext(); mChannelDataManager = appSingletons.getChannelDataManager(); mDvrSessionManager = appSingletons.getDvrSessionManger(); } @@ -72,18 +59,17 @@ public class DvrManager { * @param program the program to record * @param recordingsToOverride the possible empty list of recordings that will not be recorded */ - public void addSchedule(Program program, List<ScheduledRecording> recordingsToOverride) { + public void addSchedule(Program program, List<Recording> recordingsToOverride) { Log.i(TAG, "Adding scheduled recording of " + program + " instead of " + recordingsToOverride); - Collections.sort(recordingsToOverride, ScheduledRecording.PRIORITY_COMPARATOR); + Collections.sort(recordingsToOverride, Recording.PRIORITY_COMPARATOR); Channel c = mChannelDataManager.getChannel(program.getChannelId()); long priority = recordingsToOverride.isEmpty() ? Long.MAX_VALUE : recordingsToOverride.get(0).getPriority() - 1; - ScheduledRecording r = ScheduledRecording.builder(program) + Recording r = Recording.builder(c, program) .setPriority(priority) - .setChannelId(c.getId()) .build(); - mDataManager.addScheduledRecording(r); + mDataManager.addRecording(r); } /** @@ -93,10 +79,8 @@ public class DvrManager { Log.i(TAG, "Adding scheduled recording of channel" + channel + " starting at " + Utils.toTimeString(startTime) + " and ending at " + Utils.toTimeString(endTime)); //TODO: handle error cases - ScheduledRecording r = ScheduledRecording.builder(startTime, endTime) - .setChannelId(channel.getId()) - .build(); - mDataManager.addScheduledRecording(r); + Recording r = Recording.builder(channel, startTime, endTime).build(); + mDataManager.addRecording(r); } /** @@ -108,45 +92,12 @@ public class DvrManager { } /** - * Stops the currently recorded program - */ - public void stopRecording(final ScheduledRecording recording) { - synchronized (mListener) { - for (final Entry<Listener, Handler> entry : mListener.entrySet()) { - entry.getValue().post(new Runnable() { - @Override - public void run() { - entry.getKey().onStopRecordingRequested(recording); - } - }); - } - } - } - - /** * Removes a scheduled recording or an existing recording. */ - public void removeScheduledRecording(ScheduledRecording scheduledRecording) { - Log.i(TAG, "Removing " + scheduledRecording); - mDataManager.removeScheduledRecording(scheduledRecording); - } - - public void removeRecordedProgram(final RecordedProgram recordedProgram) { - // TODO(dvr): implement - Log.i(TAG, "To delete " + recordedProgram - + "\nyou should manually delete video data at" - + "\nadb shell rm -rf " + recordedProgram.getDataUri() - ); - Toast.makeText(mAppContext, "Deleting recorded programs is not fully implemented yet", - Toast.LENGTH_SHORT).show(); - new AsyncDbTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - ContentResolver resolver = mAppContext.getContentResolver(); - resolver.delete(recordedProgram.getUri(), null, null); - return null; - } - }.execute(); + public void removeRecording(Recording recording) { + Log.i(TAG, "Removing " + recording); + // TODO(DVR): ask the TIS to delete the recording and respond to the result. + mDataManager.removeRecording(recording); } /** @@ -156,21 +107,17 @@ public class DvrManager { * <p>Any empty list means there is no conflicts. If there is conflict the program must be * scheduled to record with a Priority lower than the first Recording in the list returned. */ - public List<ScheduledRecording> getScheduledRecordingsThatConflict(Program program) { + public List<Recording> getScheduledRecordingsThatConflict(Program program) { //TODO(DVR): move to scheduler. //TODO(DVR): deal with more than one DvrInputService - List<ScheduledRecording> overLap = mDataManager.getRecordingsThatOverlapWith(getPeriod(program)); + List<Recording> overLap = mDataManager.getRecordingsThatOverlapWith(getPeriod(program)); if (!overLap.isEmpty()) { // TODO(DVR): ignore shows that already won't record. Channel channel = mChannelDataManager.getChannel(program.getChannelId()); if (channel != null) { - TvInputInfo info = mDvrSessionManager.getTvInputInfo(channel.getInputId()); - if (info == null) { - Log.w(TAG, - "Could not find a recording TvInputInfo for " + channel.getInputId()); - return overLap; - } - int remove = Math.max(0, info.getTunerCount() - 1); + RecordingCapability recordingCapability = mDvrSessionManager + .getRecordingCapability(channel.getInputId()); + int remove = Math.max(0, recordingCapability.maxConcurrentTunedSessions - 1); if (remove >= overLap.size()) { return Collections.EMPTY_LIST; } @@ -189,7 +136,7 @@ public class DvrManager { * Checks whether {@code channel} can be tuned without any conflict with existing recordings * in progress. If there is any conflict, {@code outConflictRecordings} will be filled. */ - public boolean canTuneTo(Channel channel, List<ScheduledRecording> outConflictScheduledRecordings) { + public boolean canTuneTo(Channel channel, List<Recording> outConflictRecordings) { // TODO: implement return true; } @@ -198,29 +145,8 @@ public class DvrManager { * Returns true is the inputId supports recording. */ public boolean canRecord(String inputId) { - TvInputInfo info = mDvrSessionManager.getTvInputInfo(inputId); - return info != null && info.getTunerCount() > 0; - } - - @WorkerThread - void addListener(Listener listener, @NonNull Handler handler) { - SoftPreconditions.checkNotNull(handler); - synchronized (mListener) { - mListener.put(listener, handler); - } - } - - @WorkerThread - void removeListener(Listener listener) { - synchronized (mListener) { - mListener.remove(listener); - } - } - - /** - * Listener internally used inside dvr package. - */ - interface Listener { - void onStopRecordingRequested(ScheduledRecording scheduledRecording); + RecordingCapability recordingCapability = mDvrSessionManager + .getRecordingCapability(inputId); + return recordingCapability != null && recordingCapability.maxConcurrentTunedSessions > 0; } } diff --git a/src/com/android/tv/dvr/DvrPlayActivity.java b/src/com/android/tv/dvr/DvrPlayActivity.java index b117a7cf..872e05bd 100644 --- a/src/com/android/tv/dvr/DvrPlayActivity.java +++ b/src/com/android/tv/dvr/DvrPlayActivity.java @@ -24,7 +24,7 @@ import com.android.tv.R; import com.android.tv.TvApplication; /** - * Simple Activity to play a {@link ScheduledRecording}. + * Simple Activity to play a {@link Recording}. */ public class DvrPlayActivity extends Activity { @@ -35,11 +35,11 @@ public class DvrPlayActivity extends Activity { DvrDataManager dvrDataManager = TvApplication.getSingletons(this).getDvrDataManager(); // TODO(DVR) handle errors. - long recordingId = getIntent().getLongExtra(ScheduledRecording.RECORDING_ID_EXTRA, 0); - ScheduledRecording scheduledRecording = dvrDataManager.getScheduledRecording(recordingId); + long recordingId = getIntent().getLongExtra(Recording.RECORDING_ID_EXTRA, 0); + Recording recording = dvrDataManager.getRecording(recordingId); TextView textView = (TextView) findViewById(R.id.placeHolderText); - if (scheduledRecording != null) { - textView.setText(scheduledRecording.toString()); + if (recording != null) { + textView.setText(recording.toString()); } else { textView.setText(R.string.ut_result_not_found_title); // TODO(DVR) update error text } diff --git a/src/com/android/tv/dvr/DvrRecordingService.java b/src/com/android/tv/dvr/DvrRecordingService.java index 2f3abccf..d0e86d50 100644 --- a/src/com/android/tv/dvr/DvrRecordingService.java +++ b/src/com/android/tv/dvr/DvrRecordingService.java @@ -31,8 +31,7 @@ import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.util.Clock; -import com.android.tv.util.RecurringRunner; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.util.SoftPreconditions; /** * DVR Scheduler service. @@ -58,8 +57,6 @@ public class DvrRecordingService extends Service { context.startService(dvrSchedulerIntent); } - private final Clock mClock = Clock.SYSTEM; - private RecurringRunner mReaperRunner; private WritableDvrDataManager mDataManager; /** @@ -89,16 +86,14 @@ public class DvrRecordingService extends Service { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); // mScheduler may have been set for testing. if (mScheduler == null) { + DvrSessionManager sessionManager = singletons.getDvrSessionManger(); mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME); mHandlerThread.start(); - mScheduler = new Scheduler(mHandlerThread.getLooper(), singletons.getDvrManager(), - singletons.getDvrSessionManger(), mDataManager, - singletons.getChannelDataManager(), this, mClock, alarmManager); + mScheduler = new Scheduler(mHandlerThread.getLooper(), sessionManager, mDataManager, + this, Clock.SYSTEM, + alarmManager); } - mDataManager.addScheduledRecordingListener(mScheduler); - mReaperRunner = new RecurringRunner(this, java.util.concurrent.TimeUnit.DAYS.toMillis(1), - new ScheduledProgramReaper(mDataManager, mClock), null); - mReaperRunner.start(); + mDataManager.addListener(mScheduler); } @Override @@ -111,8 +106,7 @@ public class DvrRecordingService extends Service { @Override public void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy"); - mReaperRunner.stop(); - mDataManager.removeScheduledRecordingListener(mScheduler); + mDataManager.removeListener(mScheduler); mScheduler = null; if (mHandlerThread != null) { mHandlerThread.quit(); diff --git a/src/com/android/tv/dvr/DvrSessionManager.java b/src/com/android/tv/dvr/DvrSessionManager.java index fba05cb6..553001e2 100644 --- a/src/com/android/tv/dvr/DvrSessionManager.java +++ b/src/com/android/tv/dvr/DvrSessionManager.java @@ -16,21 +16,18 @@ package com.android.tv.dvr; -import android.annotation.TargetApi; +import android.content.ComponentName; import android.content.Context; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; -import android.media.tv.TvRecordingClient; -import android.os.Build; -import android.os.Handler; +import android.media.tv.TvContract; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.support.v4.util.ArrayMap; -import android.util.Log; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.recording.RecordingCapability; +import com.android.tv.common.recording.TvRecording; import com.android.tv.data.Channel; +import com.android.tv.util.SoftPreconditions; +import com.android.usbtuner.tvinput.UsbTunerTvInputService; /** * Manages Dvr Sessions. @@ -40,91 +37,57 @@ import com.android.tv.data.Channel; * <li>Manage capabilities (conflict)</li> * </ul> */ -@TargetApi(Build.VERSION_CODES.N) -public class DvrSessionManager extends TvInputManager.TvInputCallback { - //consider moving all of this to TvInputManagerHelper +public class DvrSessionManager { private final static String TAG = "DvrSessionManager"; - private static final boolean DEBUG = false; - private final Context mContext; - private final TvInputManager mTvInputManager; - private final ArrayMap<String, TvInputInfo> mRecordingTvInputs = new ArrayMap<>(); + private TvRecording.TvRecordingClient mRecordingClient; + private ArrayMap<String, RecordingCapability> mCapabilityMap = new ArrayMap<>(); public DvrSessionManager(Context context) { - this(context, (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE), - new Handler()); - } - - @VisibleForTesting - DvrSessionManager(Context context, TvInputManager tvInputManager, Handler handler) { SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); - mTvInputManager = tvInputManager; mContext = context.getApplicationContext(); - for (TvInputInfo info : tvInputManager.getTvInputList()) { - if (DEBUG) { - Log.d(TAG, info + " canRecord=" + info.canRecord() + " tunerCount=" + info - .getTunerCount()); - } - if (info.canRecord()) { - mRecordingTvInputs.put(info.getId(), info); + // TODO(DVR): get a session to all clients, for now just get USB a TestInput + final String inputId = TvContract + .buildInputId(new ComponentName(context, UsbTunerTvInputService.class)); + mRecordingClient = acquireDvrSession(inputId, null); + mRecordingClient.connect(inputId, new TvRecording.ClientCallback() { + @Override + public void onCapabilityReceived(RecordingCapability capability) { + mCapabilityMap.put(inputId, capability); + mRecordingClient.release(); + mRecordingClient = null; } - } - tvInputManager.registerCallback(this, handler); + }); + if (CommonFeatures.DVR.isEnabled(context)) { // STOPSHIP(DVR) + String testInputId = "com.android.tv.testinput/.TestTvInputService"; + mCapabilityMap.put(testInputId, + RecordingCapability.builder() + .setInputId(testInputId) + .setMaxConcurrentPlayingSessions(2) + .setMaxConcurrentTunedSessions(2) + .setMaxConcurrentSessionsOfAllTypes(3) + .build()); + } } - public TvRecordingClient createTvRecordingClient(String tag, - TvRecordingClient.RecordingCallback callback, Handler handler) { - return new TvRecordingClient(mContext, tag, callback, handler); + public TvRecording.TvRecordingClient acquireDvrSession(String inputId, Channel channel) { + // TODO(DVR): use input and channel or change API + TvRecording.TvRecordingClient sessionClient = new TvRecording.TvRecordingClient(mContext); + return sessionClient; } public boolean canAcquireDvrSession(String inputId, Channel channel) { - // TODO(DVR): implement checking tuner count etc. - TvInputInfo info = mRecordingTvInputs.get(inputId); - return info != null; - } - - public void releaseTvRecordingClient(TvRecordingClient recordingClient) { - recordingClient.release(); + // TODO(DVR): implement + return true; } - @Override - public void onInputAdded(String inputId) { - super.onInputAdded(inputId); - TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); - if (DEBUG) { - Log.d(TAG, "onInputAdded " + info.toString() + " canRecord=" + info.canRecord() - + " tunerCount=" + info.getTunerCount()); - } - if (info.canRecord()) { - mRecordingTvInputs.put(inputId, info); - } - } - - @Override - public void onInputRemoved(String inputId) { - super.onInputRemoved(inputId); - if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); - mRecordingTvInputs.remove(inputId); - } - - @Override - public void onInputUpdated(String inputId) { - super.onInputUpdated(inputId); - TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); - if (DEBUG) { - Log.d(TAG, "onInputUpdated " + info.toString() + " canRecord=" + info.canRecord() - + " tunerCount=" + info.getTunerCount()); - } - if (info.canRecord()) { - mRecordingTvInputs.put(inputId, info); - } else { - mRecordingTvInputs.remove(inputId); - } + public void releaseDvrSession(TvRecording.TvRecordingClient session) { + session.release(); } @Nullable - public TvInputInfo getTvInputInfo(String inputId) { - return mRecordingTvInputs.get(inputId); + public RecordingCapability getRecordingCapability(String inputId) { + return mCapabilityMap.get(inputId); } } diff --git a/src/com/android/tv/dvr/ScheduledRecording.java b/src/com/android/tv/dvr/Recording.java index 01b00459..9ecda4da 100644 --- a/src/com/android/tv/dvr/ScheduledRecording.java +++ b/src/com/android/tv/dvr/Recording.java @@ -16,44 +16,49 @@ package com.android.tv.dvr; -import android.content.ContentValues; import android.database.Cursor; +import android.net.Uri; import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Range; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.dvr.provider.DvrContract; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * A data class for one recording contents. */ @VisibleForTesting -public final class ScheduledRecording { +public final class Recording { private static final String TAG = "Recording"; - public static final String RECORDING_ID_EXTRA = "extra.dvr.recording.id"; //TODO(DVR) move + public static final String RECORDING_ID_EXTRA = "extra.dvr.recording.id"; public static final String PARAM_INPUT_ID = "input_id"; public static final long ID_NOT_SET = -1; - public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR = new Comparator<ScheduledRecording>() { + public static final Comparator<Recording> START_TIME_COMPARATOR = new Comparator<Recording>() { @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + public int compare(Recording lhs, Recording rhs) { return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs); } }; - public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR = new Comparator<ScheduledRecording>() { + public static final Comparator<Recording> PRIORITY_COMPARATOR = new Comparator<Recording>() { @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + public int compare(Recording lhs, Recording rhs) { int value = Long.compare(lhs.mPriority, rhs.mPriority); if (value == 0) { value = Long.compare(lhs.mId, rhs.mId); @@ -62,10 +67,10 @@ public final class ScheduledRecording { } }; - public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_COMPARATOR - = new Comparator<ScheduledRecording>() { + public static final Comparator<Recording> START_TIME_THEN_PRIORITY_COMPARATOR + = new Comparator<Recording>() { @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + public int compare(Recording lhs, Recording rhs) { int value = START_TIME_COMPARATOR.compare(lhs, rhs); if (value == 0) { value = PRIORITY_COMPARATOR.compare(lhs, rhs); @@ -74,15 +79,18 @@ public final class ScheduledRecording { } }; - public static Builder builder(Program p) { + public static Builder builder(Channel c, Program p) { return new Builder() - .setStartTime(p.getStartTimeUtcMillis()).setEndTime(p.getEndTimeUtcMillis()) - .setProgramId(p.getId()) + .setChannel(c) + .setStartTime(p.getStartTimeUtcMillis()) + .setEndTime(p.getEndTimeUtcMillis()) + .setPrograms(Collections.singletonList(p)) .setType(TYPE_PROGRAM); } - public static Builder builder(long startTime, long endTime) { + public static Builder builder(Channel c, long startTime, long endTime) { return new Builder() + .setChannel(c) .setStartTime(startTime) .setEndTime(endTime) .setType(TYPE_TIMED); @@ -91,11 +99,13 @@ public final class ScheduledRecording { public static final class Builder { private long mId = ID_NOT_SET; private long mPriority = Long.MAX_VALUE; - private long mChannelId; - private long mProgramId = ID_NOT_SET; + private Uri mUri; + private Channel mChannel; + private List<Program> mPrograms; private @RecordingType int mType; private long mStartTime; private long mEndTime; + private long mSize; private @RecordingState int mState; private SeasonRecording mParentSeasonRecording; @@ -111,13 +121,18 @@ public final class ScheduledRecording { return this; } - public Builder setChannelId(long channelId) { - mChannelId = channelId; + private Builder setUri(Uri uri) { + mUri = uri; return this; } - public Builder setProgramId(long programId) { - mProgramId = programId; + private Builder setChannel(Channel channel) { + mChannel = channel; + return this; + } + + public Builder setPrograms(List<Program> programs) { + mPrograms = programs; return this; } @@ -136,6 +151,11 @@ public final class ScheduledRecording { return this; } + public Builder setSize(long size) { + mSize = size; + return this; + } + public Builder setState(@RecordingState int state) { mState = state; return this; @@ -146,21 +166,28 @@ public final class ScheduledRecording { return this; } - public ScheduledRecording build() { - return new ScheduledRecording(mId, mPriority, mChannelId, mProgramId, mType, mStartTime, - mEndTime, mState, mParentSeasonRecording); + public Recording build() { + return new Recording(mId, mPriority, mUri, mChannel, mPrograms, mType, mStartTime, + mEndTime, mSize, + mState, mParentSeasonRecording); } } /** * Creates {@link Builder} object from the given original {@code Recording}. */ - public static Builder buildFrom(ScheduledRecording orig) { + public static Builder buildFrom(Recording orig) { return new Builder() - .setId(orig.mId).setChannelId(orig.mChannelId) - .setEndTime(orig.mEndTimeMs).setParentSeasonRecording(orig.mParentSeasonRecording) - .setProgramId(orig.mProgramId) - .setStartTime(orig.mStartTimeMs).setState(orig.mState).setType(orig.mType); + .setId(orig.mId) + .setChannel(orig.mChannel) + .setEndTime(orig.mEndTimeMs) + .setParentSeasonRecording(orig.mParentSeasonRecording) + .setPrograms(orig.mPrograms) + .setSize(orig.mMediaSize) + .setStartTime(orig.mStartTimeMs) + .setState(orig.mState) + .setType(orig.mType) + .setUri(orig.mUri); } @Retention(RetentionPolicy.SOURCE) @@ -169,7 +196,6 @@ public final class ScheduledRecording { public @interface RecordingState {} public static final int STATE_RECORDING_NOT_STARTED = 0; public static final int STATE_RECORDING_IN_PROGRESS = 1; - @Deprecated // It is not used. public static final int STATE_RECORDING_UNEXPECTEDLY_STOPPED = 2; public static final int STATE_RECORDING_FINISHED = 3; public static final int STATE_RECORDING_FAILED = 4; @@ -189,46 +215,20 @@ public final class ScheduledRecording { @RecordingType private final int mType; /** - * Use this projection if you want to create {@link ScheduledRecording} object using {@link #fromCursor}. + * Use this projection if you want to create {@link Recording} object using {@link #fromCursor}. */ public static final String[] PROJECTION = { - // Columns must match what is read in Recording.fromCursor() - DvrContract.Recordings._ID, - DvrContract.Recordings.COLUMN_PRIORITY, - DvrContract.Recordings.COLUMN_TYPE, - DvrContract.Recordings.COLUMN_CHANNEL_ID, - DvrContract.Recordings.COLUMN_PROGRAM_ID, - DvrContract.Recordings.COLUMN_START_TIME_UTC_MILLIS, - DvrContract.Recordings.COLUMN_END_TIME_UTC_MILLIS, - DvrContract.Recordings.COLUMN_STATE}; - /** - * Creates {@link ScheduledRecording} object from the given {@link Cursor}. - */ - public static ScheduledRecording fromCursor(Cursor c) { - int index = -1; - return new Builder() - .setId(c.getLong(++index)) - .setPriority(c.getLong(++index)) - .setType(recordingType(c.getString(++index))) - .setChannelId(c.getLong(++index)) - .setProgramId(c.getLong(++index)) - .setStartTime(c.getLong(++index)) - .setEndTime(c.getLong(++index)) - .setState(recordingState(c.getString(++index))) - .build(); - } - - public static ContentValues toContentValues(ScheduledRecording r) { - ContentValues values = new ContentValues(); - values.put(DvrContract.Recordings.COLUMN_CHANNEL_ID, r.getChannelId()); - values.put(DvrContract.Recordings.COLUMN_PROGRAM_ID, r.getProgramId()); - values.put(DvrContract.Recordings.COLUMN_PRIORITY, r.getPriority()); - values.put(DvrContract.Recordings.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs()); - values.put(DvrContract.Recordings.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs()); - values.put(DvrContract.Recordings.COLUMN_STATE, r.getState()); - values.put(DvrContract.Recordings.COLUMN_TYPE, r.getType()); - return values; - } + // Columns must match what is read in Recording.fromCursor() + DvrContract.Recordings._ID, + DvrContract.Recordings.COLUMN_PRIORITY, + DvrContract.Recordings.COLUMN_TYPE, + DvrContract.Recordings.COLUMN_URI, + DvrContract.Recordings.COLUMN_CHANNEL_ID, + DvrContract.Recordings.COLUMN_START_TIME_UTC_MILLIS, + DvrContract.Recordings.COLUMN_END_TIME_UTC_MILLIS, + DvrContract.Recordings.COLUMN_MEDIA_SIZE, + DvrContract.Recordings.COLUMN_STATE + }; /** * The ID internal to Live TV @@ -243,30 +243,53 @@ public final class ScheduledRecording { */ private final long mPriority; + /** + * The {@link Uri} is used as its identifier with the TIS. + * Note: If the state is STATE_RECORDING_NOT_STARTED, this might be {@code null}. + */ + @Nullable + private final Uri mUri; - private final long mChannelId; /** - * Optional id of the associated program. - * + * Note: mChannel and mPrograms should be loaded from a separate storage not + * from TvProvider, because info from TvProvider can be removed or edited later. */ - private final long mProgramId; + @NonNull + private final Channel mChannel; + /** + * Recorded program info. Its size is usually 1. But, when a channel is recorded by given time + * range, multiple programs can be recorded in one recording. + */ + @NonNull + private final List<Program> mPrograms; private final long mStartTimeMs; private final long mEndTimeMs; + private final long mMediaSize; @RecordingState private final int mState; private final SeasonRecording mParentSeasonRecording; - private ScheduledRecording(long id, long priority, long channelId, long programId, - @RecordingType int type, long startTime, long endTime, + private Recording(long id, long priority, Uri uri, Channel channel, List<Program> programs, + @RecordingType int type, long startTime, long endTime, long size, @RecordingState int state, SeasonRecording parentSeasonRecording) { mId = id; mPriority = priority; - mChannelId = channelId; - mProgramId = programId; + if (uri == null && id >= 0 && channel != null) { + uri = new Uri.Builder() + .scheme("record") + .authority("com.android.tv") + .appendPath(Long.toString(mId)) + .appendQueryParameter(PARAM_INPUT_ID, channel.getInputId()) + .build(); + } + mUri = uri; + mChannel = channel; + mPrograms = programs == null ? Collections.EMPTY_LIST : new ArrayList<>(programs); mType = type; mStartTimeMs = startTime; mEndTimeMs = endTime; + mMediaSize = size; mState = state; mParentSeasonRecording = parentSeasonRecording; } @@ -281,17 +304,24 @@ public final class ScheduledRecording { } /** + * Returns {@link android.net.Uri} representing the recording. + */ + public Uri getUri() { + return mUri; + } + + /** * Returns recorded {@link Channel}. */ - public long getChannelId() { - return mChannelId; + public Channel getChannel() { + return mChannel; } /** - * Return the optional program id + * Returns a list of recorded {@link Program}. */ - public long getProgramId() { - return mProgramId; + public List<Program> getPrograms() { + return mPrograms; } /** @@ -316,6 +346,13 @@ public final class ScheduledRecording { } /** + * Returns file size which this record consumes. + */ + public long getSize() { + return mMediaSize; + } + + /** * Returns the state. The possible states are {@link #STATE_RECORDING_FINISHED}, * {@link #STATE_RECORDING_IN_PROGRESS} and {@link #STATE_RECORDING_UNEXPECTEDLY_STOPPED}. */ @@ -339,6 +376,30 @@ public final class ScheduledRecording { } /** + * Creates {@link Recording} object from the given {@link Cursor}. + */ + public static Recording fromCursor(Cursor c, Channel channel, List<Program> programs) { + Builder builder = new Builder(); + int index = -1; + builder.setId(c.getLong(++index)); + builder.setPriority(c.getLong(++index)); + builder.setType(recordingType(c.getString(++index))); + String uri = c.getString(++index); + if (uri != null) { + builder.setUri(Uri.parse(uri)); + } + // Skip channel. + ++index; + builder.setStartTime(c.getLong(++index)); + builder.setEndTime(c.getLong(++index)); + builder.setSize(c.getLong(++index)); + builder.setState(recordingState(c.getString(++index))); + builder.setChannel(channel); + builder.setPrograms(programs); + return builder.build(); + } + + /** * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. */ private static @RecordingType int recordingType(String type) { @@ -398,7 +459,7 @@ public final class ScheduledRecording { @Override public String toString() { - return "ScheduledRecording[" + mId + return "Recording[" + mId + "]" + "(startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) diff --git a/src/com/android/tv/dvr/RecordingTask.java b/src/com/android/tv/dvr/RecordingTask.java index 804485b3..3bed5e77 100644 --- a/src/com/android/tv/dvr/RecordingTask.java +++ b/src/com/android/tv/dvr/RecordingTask.java @@ -16,8 +16,6 @@ package com.android.tv.dvr; -import android.media.tv.TvContract; -import android.media.tv.TvRecordingClient; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -26,9 +24,10 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.recording.TvRecording; import com.android.tv.data.Channel; import com.android.tv.util.Clock; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.Utils; import java.util.concurrent.TimeUnit; @@ -40,10 +39,9 @@ import java.util.concurrent.TimeUnit; * There is only one looper so messages must be handled quickly or start a separate thread. */ @WorkerThread -class RecordingTask extends TvRecordingClient.RecordingCallback - implements Handler.Callback, DvrManager.Listener { +class RecordingTask extends TvRecording.ClientCallback implements Handler.Callback { private static final String TAG = "RecordingTask"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; //STOPSHIP(DVR) @VisibleForTesting static final int MESSAGE_INIT = 1; @@ -53,10 +51,11 @@ class RecordingTask extends TvRecordingClient.RecordingCallback static final int MESSAGE_STOP_RECORDING = 3; @VisibleForTesting - static final long MS_BEFORE_START = TimeUnit.SECONDS.toMillis(5); + static long MS_BEFORE_START = TimeUnit.SECONDS.toMillis(5); @VisibleForTesting - static final long MS_AFTER_END = TimeUnit.SECONDS.toMillis(5); + static long MS_AFTER_END = TimeUnit.SECONDS.toMillis(5); + //STOPSHIP(DVR) don't use enums. @VisibleForTesting enum State { NOT_STARTED, @@ -65,33 +64,27 @@ class RecordingTask extends TvRecordingClient.RecordingCallback CONNECTED, RECORDING_START_REQUESTED, RECORDING_STARTED, - RECORDING_STOP_REQUESTED, ERROR, RELEASED, } private final DvrSessionManager mSessionManager; - private final DvrManager mDvrManager; private final WritableDvrDataManager mDataManager; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - private TvRecordingClient mTvRecordingClient; + private TvRecording.TvRecordingClient mSession; private Handler mHandler; - private ScheduledRecording mScheduledRecording; - private final Channel mChannel; + private Recording mRecording; private State mState = State.NOT_STARTED; private final Clock mClock; - RecordingTask(ScheduledRecording scheduledRecording, Channel channel, - DvrManager dvrManager, DvrSessionManager sessionManager, + RecordingTask(Recording recording, DvrSessionManager sessionManager, WritableDvrDataManager dataManager, Clock clock) { - mScheduledRecording = scheduledRecording; - mChannel = channel; + mRecording = recording; mSessionManager = sessionManager; mDataManager = dataManager; mClock = clock; - mDvrManager = dvrManager; - if (DEBUG) Log.d(TAG, "created recording task " + mScheduledRecording); + if (DEBUG) Log.d(TAG, "created recording task " + mRecording); } public void setHandler(Handler handler) { @@ -125,45 +118,60 @@ class RecordingTask extends TvRecordingClient.RecordingCallback } return true; } catch (Exception e) { - Log.w(TAG, "Error processing message " + msg + " for " + mScheduledRecording, e); + Log.w(TAG, "Error processing message " + msg + " for " + mRecording, e); failAndQuit(); } return false; } @Override - public void onTuned(Uri channelUri) { - if (DEBUG) { - Log.d(TAG, "onTuned"); - } - super.onTuned(channelUri); + public void onConnected() { + if (DEBUG) Log.d(TAG, "onConnected"); + super.onConnected(); mState = State.CONNECTED; - if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MESSAGE_START_RECORDING, - mScheduledRecording.getStartTimeMs() - MS_BEFORE_START)) { - mState = State.ERROR; - return; - } } + @Override + public void onDisconnected() { + if (DEBUG) Log.d(TAG, "onDisconnected"); + super.onDisconnected(); + //Do nothing + } @Override - public void onRecordingStopped(Uri recordedProgramUri) { - super.onRecordingStopped(recordedProgramUri); - mState = State.CONNECTED; - updateRecording(ScheduledRecording.buildFrom(mScheduledRecording) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); - sendRemove(); + public void onRecordDeleted(Uri mediaUri) { + if (DEBUG) Log.d(TAG, "onRecordDeleted " + mediaUri); + super.onRecordDeleted(mediaUri); + SoftPreconditions.checkState(false, TAG, "unexpected onRecordDeleted"); + } @Override - public void onError(int reason) { - if (DEBUG) Log.d(TAG, "onError reason " + reason); - super.onError(reason); + public void onRecordDeleteFailed(Uri mediaUri, int reason) { + if (DEBUG) Log.d(TAG, "onRecordDeleteFailed " + mediaUri + ", " + reason); + super.onRecordDeleteFailed(mediaUri, reason); + SoftPreconditions.checkState(false, TAG, "unexpected onRecordDeleteFailed"); + } + + @Override + public void onRecordStarted(Uri mediaUri) { + if (DEBUG) Log.d(TAG, "onRecordStarted " + mediaUri); + super.onRecordStarted(mediaUri); + mState = State.RECORDING_STARTED; + updateRecording(Recording.buildFrom(mRecording) + .setState(Recording.STATE_RECORDING_IN_PROGRESS) + .build()); + } + + @Override + public void onRecordStopped(Uri mediaUri, @TvRecording.RecordStopReason int reason) { + if (DEBUG) Log.d(TAG, "onRecordStopped " + mediaUri + " reason " + reason); + super.onRecordStopped(mediaUri, reason); // TODO(dvr) handle success switch (reason) { default: - updateRecording(ScheduledRecording.buildFrom(mScheduledRecording) - .setState(ScheduledRecording.STATE_RECORDING_FAILED) + updateRecording(Recording.buildFrom(mRecording) + .setState(Recording.STATE_RECORDING_FAILED) .build()); } release(); @@ -171,78 +179,67 @@ class RecordingTask extends TvRecordingClient.RecordingCallback } private void handleInit() { - if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording); //TODO check recording preconditions - - if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) { - Log.w(TAG, "End time already past, not recording " + mScheduledRecording); + Channel channel = mRecording.getChannel(); + if (channel == null) { + Log.w(TAG, "Null channel for " + mRecording); failAndQuit(); return; } - if (mChannel == null) { - Log.w(TAG, "Null channel for " + mScheduledRecording); - failAndQuit(); - return; - } - if (mChannel.getId() != mScheduledRecording.getChannelId()) { - Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording " - + mScheduledRecording); - failAndQuit(); - return; - } - - String inputId = mChannel.getInputId(); - if (mSessionManager.canAcquireDvrSession(inputId, mChannel)) { - mTvRecordingClient = mSessionManager - .createTvRecordingClient("recordingTask-" + mScheduledRecording.getId(), this, - mHandler); + String inputId = channel.getInputId(); + if (mSessionManager.canAcquireDvrSession(inputId, channel)) { + mSession = mSessionManager.acquireDvrSession(inputId, channel); mState = State.SESSION_ACQUIRED; } else { - Log.w(TAG, "Unable to acquire a session for " + mScheduledRecording); + Log.w(TAG, "Unable to acquire a session for " + mRecording); failAndQuit(); return; } - mDvrManager.addListener(this, mHandler); - mTvRecordingClient.tune(inputId, mChannel.getUri()); + + mSession.connect(inputId, this); mState = State.CONNECTION_PENDING; + + if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MESSAGE_START_RECORDING, + mRecording.getStartTimeMs() - MS_BEFORE_START)) { + mState = State.ERROR; + return; + } } private void failAndQuit() { - if (DEBUG) Log.d(TAG, "failAndQuit"); - updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); + updateRecordingState(Recording.STATE_RECORDING_FAILED); mState = State.ERROR; sendRemove(); } private void sendRemove() { - if (DEBUG) Log.d(TAG, "sendRemove"); if (mHandler != null) { mHandler.sendEmptyMessage(Scheduler.HandlerWrapper.MESSAGE_REMOVE); } } private void handleStartRecording() { - if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording); + if (DEBUG)Log.d(TAG, "handleStartRecording " + mRecording); // TODO(DVR) handle errors - long programId = mScheduledRecording.getProgramId(); - mTvRecordingClient.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null - : TvContract.buildProgramUri(programId)); - updateRecording(ScheduledRecording.buildFrom(mScheduledRecording) - .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS).build()); - mState = State.RECORDING_STARTED; - + Channel channel = mRecording.getChannel(); + mSession.startRecord(channel.getUri(), getIdAsMediaUri(mRecording)); + mState= State.RECORDING_START_REQUESTED; if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MESSAGE_STOP_RECORDING, - mScheduledRecording.getEndTimeMs() + MS_AFTER_END)) { + mRecording.getEndTimeMs() + MS_AFTER_END)) { mState = State.ERROR; return; } } private void handleStopRecording() { - if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording); - mTvRecordingClient.stopRecording(); - mState = State.RECORDING_STOP_REQUESTED; + if (DEBUG)Log.d(TAG, "handleStopRecording " + mRecording); + mSession.stopRecord(); + // TODO: once we add an API to notify successful completion of recording, + // the following parts need to be moved to the listener implementation. + updateRecording(Recording.buildFrom(mRecording) + .setState(Recording.STATE_RECORDING_FINISHED).build()); + sendRemove(); } @VisibleForTesting @@ -251,10 +248,10 @@ class RecordingTask extends TvRecordingClient.RecordingCallback } private void release() { - if (mTvRecordingClient != null) { - mSessionManager.releaseTvRecordingClient(mTvRecordingClient); + if (mSession != null) { + mSession.release(); + mSessionManager.releaseDvrSession(mSession); } - mDvrManager.removeListener(this); } private boolean sendEmptyMessageAtAbsoluteTime(int what, long when) { @@ -267,55 +264,28 @@ class RecordingTask extends TvRecordingClient.RecordingCallback return mHandler.sendEmptyMessageDelayed(what, delay); } - private void updateRecordingState(@ScheduledRecording.RecordingState int state) { - updateRecording(ScheduledRecording.buildFrom(mScheduledRecording).setState(state).build()); + private void updateRecordingState(@Recording.RecordingState int state) { + updateRecording(Recording.buildFrom(mRecording).setState(state).build()); } - @VisibleForTesting - static Uri getIdAsMediaUri(ScheduledRecording scheduledRecording) { + @VisibleForTesting static Uri getIdAsMediaUri(Recording recording) { // TODO define the URI format - return new Uri.Builder().appendPath(String.valueOf(scheduledRecording.getId())).build(); + return new Uri.Builder().appendPath(String.valueOf(recording.getId())).build(); } - private void updateRecording(ScheduledRecording updatedScheduledRecording) { - if (DEBUG) Log.d(TAG, "updateScheduledRecording " + updatedScheduledRecording); - mScheduledRecording = updatedScheduledRecording; + private void updateRecording(Recording updatedRecording) { + if (DEBUG) Log.d(TAG, "updateRecording " + updatedRecording); + mRecording = updatedRecording; mMainThreadHandler.post(new Runnable() { @Override public void run() { - mDataManager.updateScheduledRecording(mScheduledRecording); + mDataManager.updateRecording(mRecording); } }); } @Override - public void onStopRecordingRequested(ScheduledRecording recording) { - if (recording.getId() != mScheduledRecording.getId()) { - return; - } - switch (mState) { - case RECORDING_STARTED: - mHandler.removeMessages(MESSAGE_STOP_RECORDING); - handleStopRecording(); - break; - case RECORDING_STOP_REQUESTED: - // Do nothing - break; - case NOT_STARTED: - case SESSION_ACQUIRED: - case CONNECTION_PENDING: - case CONNECTED: - case RECORDING_START_REQUESTED: - case ERROR: - case RELEASED: - default: - sendRemove(); - break; - } - } - - @Override public String toString() { - return getClass().getName() + "(" + mScheduledRecording + ")"; + return getClass().getName() + "(" + mRecording + ")"; } } diff --git a/src/com/android/tv/dvr/ScheduledProgramReaper.java b/src/com/android/tv/dvr/ScheduledProgramReaper.java deleted file mode 100644 index 9053eaec..00000000 --- a/src/com/android/tv/dvr/ScheduledProgramReaper.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr; - -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; - -import com.android.tv.util.Clock; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Deletes {@link ScheduledRecording} older than {@value @DAYS} days. - */ -class ScheduledProgramReaper implements Runnable { - - @VisibleForTesting - static final int DAYS = 2; - private final WritableDvrDataManager mDvrDataManager; - private final Clock mClock; - - ScheduledProgramReaper(WritableDvrDataManager dvrDataManager, Clock clock) { - mDvrDataManager = dvrDataManager; - mClock = clock; - } - - @Override - @MainThread - public void run() { - List<ScheduledRecording> recordings = mDvrDataManager.getAllScheduledRecordings(); - long cutoff = mClock.currentTimeMillis() - TimeUnit.DAYS.toMillis(DAYS); - for (ScheduledRecording r : recordings) { - if (r.getEndTimeMs() < cutoff) { - mDvrDataManager.removeScheduledRecording(r); - } - } - } -} diff --git a/src/com/android/tv/dvr/Scheduler.java b/src/com/android/tv/dvr/Scheduler.java index ff9bde68..8070f8a6 100644 --- a/src/com/android/tv/dvr/Scheduler.java +++ b/src/com/android/tv/dvr/Scheduler.java @@ -28,8 +28,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.Range; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; import com.android.tv.util.Clock; import java.util.List; @@ -39,7 +37,7 @@ import java.util.concurrent.TimeUnit; * The core class to manage schedule and run actual recording. */ @VisibleForTesting -public class Scheduler implements DvrDataManager.ScheduledRecordingListener { +public class Scheduler implements DvrDataManager.Listener { private static final String TAG = "Scheduler"; private static final boolean DEBUG = false; @@ -53,9 +51,9 @@ public class Scheduler implements DvrDataManager.ScheduledRecordingListener { public static final int MESSAGE_REMOVE = 999; private final long mId; - HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording, RecordingTask recordingTask) { + HandlerWrapper(Looper looper, Recording recording, RecordingTask recordingTask) { super(looper, recordingTask); - mId = scheduledRecording.getId(); + mId = recording.getId(); } @Override @@ -75,32 +73,27 @@ public class Scheduler implements DvrDataManager.ScheduledRecordingListener { private final Looper mLooper; private final DvrSessionManager mSessionManager; private final WritableDvrDataManager mDataManager; - private final DvrManager mDvrManager; - private final ChannelDataManager mChannelDataManager; private final Context mContext; private final Clock mClock; private final AlarmManager mAlarmManager; - public Scheduler(Looper looper, DvrManager dvrManager, DvrSessionManager sessionManager, - WritableDvrDataManager dataManager, ChannelDataManager channelDataManager, - Context context, Clock clock, + public Scheduler(Looper looper, DvrSessionManager sessionManager, + WritableDvrDataManager dataManager, Context context, Clock clock, AlarmManager alarmManager) { mLooper = looper; - mDvrManager = dvrManager; mSessionManager = sessionManager; mDataManager = dataManager; - mChannelDataManager = channelDataManager; mContext = context; mClock = clock; mAlarmManager = alarmManager; } private void updatePendingRecordings() { - List<ScheduledRecording> scheduledRecordings = mDataManager.getRecordingsThatOverlapWith( + List<Recording> recordings = mDataManager.getRecordingsThatOverlapWith( new Range(mClock.currentTimeMillis(), mClock.currentTimeMillis() + SOON_DURATION_IN_MS)); // TODO(DVR): handle removing and updating exiting recordings. - for (ScheduledRecording r : scheduledRecordings) { + for (Recording r : recordings) { scheduleRecordingSoon(r); } } @@ -115,18 +108,18 @@ public class Scheduler implements DvrDataManager.ScheduledRecordingListener { } @Override - public void onScheduledRecordingAdded(ScheduledRecording scheduledRecording) { - if (DEBUG) Log.d(TAG, "added " + scheduledRecording); - if (startsWithin(scheduledRecording, SOON_DURATION_IN_MS)) { - scheduleRecordingSoon(scheduledRecording); + public void onRecordingAdded(Recording recording) { + if (DEBUG) Log.d(TAG, "added " + recording); + if (startsWithin(recording, SOON_DURATION_IN_MS)) { + scheduleRecordingSoon(recording); } else { updateNextAlarm(); } } @Override - public void onScheduledRecordingRemoved(ScheduledRecording scheduledRecording) { - long id = scheduledRecording.getId(); + public void onRecordingRemoved(Recording recording) { + long id = recording.getId(); HandlerWrapper wrapper = mPendingRecordings.get(id); if (wrapper != null) { wrapper.removeCallbacksAndMessages(null); @@ -137,18 +130,16 @@ public class Scheduler implements DvrDataManager.ScheduledRecordingListener { } @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording scheduledRecording) { + public void onRecordingStatusChanged(Recording recording) { //TODO(DVR): implement } - private void scheduleRecordingSoon(ScheduledRecording scheduledRecording) { - Channel channel = mChannelDataManager.getChannel(scheduledRecording.getChannelId()); - RecordingTask recordingTask = new RecordingTask(scheduledRecording, channel, mDvrManager, - mSessionManager, mDataManager, mClock); - HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, scheduledRecording, - recordingTask); + private void scheduleRecordingSoon(Recording recording) { + RecordingTask recordingTask = new RecordingTask(recording, mSessionManager, mDataManager, + mClock); + HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, recording, recordingTask); recordingTask.setHandler(handlerWrapper); - mPendingRecordings.put(scheduledRecording.getId(), handlerWrapper); + mPendingRecordings.put(recording.getId(), handlerWrapper); handlerWrapper.sendEmptyMessage(RecordingTask.MESSAGE_INIT); } @@ -173,7 +164,7 @@ public class Scheduler implements DvrDataManager.ScheduledRecordingListener { } @VisibleForTesting - boolean startsWithin(ScheduledRecording scheduledRecording, long durationInMs) { - return mClock.currentTimeMillis() >= scheduledRecording.getStartTimeMs() - durationInMs; + boolean startsWithin(Recording recording, long durationInMs) { + return mClock.currentTimeMillis() >= recording.getStartTimeMs() - durationInMs; } } diff --git a/src/com/android/tv/dvr/SeasonRecording.java b/src/com/android/tv/dvr/SeasonRecording.java index 7f89e135..074ef017 100644 --- a/src/com/android/tv/dvr/SeasonRecording.java +++ b/src/com/android/tv/dvr/SeasonRecording.java @@ -29,7 +29,7 @@ public class SeasonRecording { */ private static final int ALL_SEASON = -1; - private List<ScheduledRecording> mSchedule; + private List<Recording> mSchedule; private String mTitle; private int mSeasonNumber; } diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java index 0b8a4c99..87809701 100644 --- a/src/com/android/tv/dvr/WritableDvrDataManager.java +++ b/src/com/android/tv/dvr/WritableDvrDataManager.java @@ -29,7 +29,7 @@ interface WritableDvrDataManager extends DvrDataManager { /** * Add a new recording. */ - void addScheduledRecording(ScheduledRecording scheduledRecording); + void addRecording(Recording recording); /** * Add a season recording/ @@ -39,7 +39,7 @@ interface WritableDvrDataManager extends DvrDataManager { /** * Remove a recording. */ - void removeScheduledRecording(ScheduledRecording ScheduledRecording); + void removeRecording(Recording Recording); /** * Remove a season schedule. @@ -49,5 +49,5 @@ interface WritableDvrDataManager extends DvrDataManager { /** * Update an existing recording. */ - void updateScheduledRecording(ScheduledRecording r); + void updateRecording(Recording r); } diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java index 6058aa54..3fc6e4a9 100644 --- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java +++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java @@ -21,12 +21,19 @@ import android.database.Cursor; import android.os.AsyncTask; import android.support.annotation.Nullable; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.data.Channel; +import com.android.tv.data.Program; +import com.android.tv.dvr.Recording; +import com.android.tv.dvr.provider.DvrContract.DvrChannels; +import com.android.tv.dvr.provider.DvrContract.DvrPrograms; +import com.android.tv.dvr.provider.DvrContract.RecordingToPrograms; import com.android.tv.dvr.provider.DvrContract.Recordings; import com.android.tv.util.NamedThreadFactory; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -80,14 +87,14 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result> * The id will be -1 if there was an error. */ public abstract static class AsyncAddRecordingTask - extends AsyncDvrDbTask<ScheduledRecording, Void, List<ScheduledRecording>> { + extends AsyncDvrDbTask<Recording, Void, List<Recording>> { public AsyncAddRecordingTask(Context context) { super(context); } @Override - protected final List<ScheduledRecording> doInDvrBackground(ScheduledRecording... params) { + protected final List<Recording> doInDvrBackground(Recording... params) { return sDbHelper.insertRecordings(params); } } @@ -99,13 +106,13 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result> * if no match was found. The count is expected to be exactly 1 for each recording. */ public abstract static class AsyncUpdateRecordingTask - extends AsyncDvrDbTask<ScheduledRecording, Void, List<Integer>> { + extends AsyncDvrDbTask<Recording, Void, List<Integer>> { public AsyncUpdateRecordingTask(Context context) { super(context); } @Override - protected final List<Integer> doInDvrBackground(ScheduledRecording... params) { + protected final List<Integer> doInDvrBackground(Recording... params) { return sDbHelper.updateRecordings(params); } } @@ -117,43 +124,91 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result> * if no match was found. The count is expected to be exactly 1 for each recording. */ public abstract static class AsyncDeleteRecordingTask - extends AsyncDvrDbTask<ScheduledRecording, Void, List<Integer>> { + extends AsyncDvrDbTask<Recording, Void, List<Integer>> { public AsyncDeleteRecordingTask(Context context) { super(context); } @Override - protected final List<Integer> doInDvrBackground(ScheduledRecording... params) { + protected final List<Integer> doInDvrBackground(Recording... params) { return sDbHelper.deleteRecordings(params); } } public abstract static class AsyncDvrQueryTask - extends AsyncDvrDbTask<Void, Void, List<ScheduledRecording>> { + extends AsyncDvrDbTask<Void, Void, List<Recording>> { public AsyncDvrQueryTask(Context context) { super(context); } @Override @Nullable - protected final List<ScheduledRecording> doInDvrBackground(Void... params) { + protected final List<Recording> doInDvrBackground(Void... params) { if (isCancelled()) { return null; } + // Read Channels Table. + Map<Long, Channel> channelMap = new HashMap<>(); + try (Cursor c = sDbHelper.query(DvrChannels.TABLE_NAME, Channel.PROJECTION_DVR)) { + while (c.moveToNext() && !isCancelled()) { + Channel channel = Channel.fromDvrCursor(c); + channelMap.put(channel.getDvrId(), channel); + } + } + + if (isCancelled()) { + return null; + } + // Read Programs Table. + Map<Long, Program> programMap = new HashMap<>(); + try (Cursor c = sDbHelper.query(DvrPrograms.TABLE_NAME, Program.PROJECTION_DVR)) { + while (c.moveToNext() && !isCancelled()) { + Program program = Program.fromDvrCursor(c); + programMap.put(program.getDvrId(), program); + } + } if (isCancelled()) { return null; } + // Read Mapping Table. + Map<Long, List<Long>> recordingToProgramMap = new HashMap<>(); + try (Cursor c = sDbHelper.query(RecordingToPrograms.TABLE_NAME, new String[] { + RecordingToPrograms.COLUMN_RECORDING_ID, + RecordingToPrograms.COLUMN_PROGRAM_ID})) { + while (c.moveToNext() && !isCancelled()) { + long recordingId = c.getLong(0); + List<Long> programList = recordingToProgramMap.get(recordingId); + if (programList == null) { + programList = new ArrayList<>(); + recordingToProgramMap.put(recordingId, programList); + } + programList.add(c.getLong(1)); + } + } + if (isCancelled()) { return null; } - List<ScheduledRecording> scheduledRecordings = new ArrayList<>(); - try (Cursor c = sDbHelper.query(Recordings.TABLE_NAME, ScheduledRecording.PROJECTION)) { + List<Recording> recordings = new ArrayList<>(); + try (Cursor c = sDbHelper.query(Recordings.TABLE_NAME, Recording.PROJECTION)) { + int idIndex = c.getColumnIndex(Recordings._ID); + int channelIndex = c.getColumnIndex(Recordings.COLUMN_CHANNEL_ID); while (c.moveToNext() && !isCancelled()) { - scheduledRecordings.add(ScheduledRecording.fromCursor(c)); + Channel channel = channelMap.get(c.getLong(channelIndex)); + List<Program> programs = null; + long recordingId = c.getLong(idIndex); + List<Long> programIds = recordingToProgramMap.get(recordingId); + if (programIds != null) { + programs = new ArrayList<>(); + for (long programId : programIds) { + programs.add(programMap.get(programId)); + } + } + recordings.add(Recording.fromCursor(c, channel, programs)); } } - return scheduledRecordings; + return recordings; } } } diff --git a/src/com/android/tv/dvr/provider/DvrContract.java b/src/com/android/tv/dvr/provider/DvrContract.java index 192cc17b..e6ce4141 100644 --- a/src/com/android/tv/dvr/provider/DvrContract.java +++ b/src/com/android/tv/dvr/provider/DvrContract.java @@ -73,23 +73,22 @@ public final class DvrContract { public static final String COLUMN_TYPE = "type"; /** - * The ID of the channel for recording. + * The URI string for the recorded media. * - * <p>This is a required field. + * <p>This field can be null if the media is not recorded yet. * - * <p>Type: INTEGER (long) + * <p>Type: String */ - public static final String COLUMN_CHANNEL_ID = "channel_id"; - + public static final String COLUMN_URI = "uri"; /** - * The ID of the associated program for recording. + * The ID of the channel for recording. * - * <p>This is an optional field. + * <p>This is a required field. It's not an ID in TvProvider, but in DVR database. * * <p>Type: INTEGER (long) */ - public static final String COLUMN_PROGRAM_ID = "program_id"; + public static final String COLUMN_CHANNEL_ID = "channel_id"; /** * The start time of this recording, in milliseconds since the epoch. @@ -110,6 +109,13 @@ public final class DvrContract { public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; /** + * The size of the stored media in bytes. + * + * <p>Type: INTEGER (long) + */ + public static final String COLUMN_MEDIA_SIZE = "media_size"; + + /** * The state of this recording. * * <p>This value should be one of the followings: {@link #STATE_RECORDING_NOT_STARTED}, @@ -121,9 +127,49 @@ public final class DvrContract { * <p>Type: String */ public static final String COLUMN_STATE = "state"; + } - private Recordings() { } + /** + * Column definition for channels for recording. + * + * <p>This is the subset of {@link android.media.tv.TvContract.Channels}. + */ + public static final class DvrChannels implements BaseColumns { + /** The table name. */ + public static final String TABLE_NAME = "dvr_channels"; } - private DvrContract() { } + /** + * Column definition for programs for recording. + * + * <p>This is the subset of {@link android.media.tv.TvContract.Programs}. + */ + public static final class DvrPrograms implements BaseColumns { + /** The table name. */ + public static final String TABLE_NAME = "dvr_programs"; + } + + /** Column definition for the mapping from recording to programs */ + public static final class RecordingToPrograms implements BaseColumns { + /** The table name. */ + public static final String TABLE_NAME = "recording_to_programs"; + + /** + * The ID of the recording. + * + * <p>This is a required field. + * + * <p>Type: INTEGER (long) + */ + public static final String COLUMN_RECORDING_ID = "recording_id"; + + /** + * The ID of the program. + * + * <p>This is a required field. It's not an ID in TvProvider, but in DVR database. + * + * <p>Type: INTEGER (long) + */ + public static final String COLUMN_PROGRAM_ID = "program_id"; + } } diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java index bdba8ac3..2445e935 100644 --- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java +++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java @@ -24,7 +24,11 @@ import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.util.Log; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.data.Channel; +import com.android.tv.dvr.Recording; +import com.android.tv.dvr.provider.DvrContract.DvrChannels; +import com.android.tv.dvr.provider.DvrContract.DvrPrograms; +import com.android.tv.dvr.provider.DvrContract.RecordingToPrograms; import com.android.tv.dvr.provider.DvrContract.Recordings; import java.util.ArrayList; @@ -37,22 +41,49 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "DvrDatabaseHelper"; private static final boolean DEBUG = true; - private static final int DATABASE_VERSION = 4; + private static final int DATABASE_VERSION = 2; private static final String DB_NAME = "dvr.db"; private static final String SQL_CREATE_RECORDINGS = "CREATE TABLE " + Recordings.TABLE_NAME + "(" - + Recordings._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Recordings.COLUMN_PRIORITY + " INTEGER DEFAULT " + Long.MAX_VALUE + "," + + Recordings._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Recordings.COLUMN_PRIORITY + + " INTEGER DEFAULT " + Long.MAX_VALUE + "," + Recordings.COLUMN_TYPE + " TEXT NOT NULL," + + Recordings.COLUMN_URI + " TEXT," + Recordings.COLUMN_CHANNEL_ID + " INTEGER NOT NULL," - + Recordings.COLUMN_PROGRAM_ID + " INTEGER ," + Recordings.COLUMN_START_TIME_UTC_MILLIS + " INTEGER NOT NULL," + Recordings.COLUMN_END_TIME_UTC_MILLIS + " INTEGER NOT NULL," + + Recordings.COLUMN_MEDIA_SIZE + " INTEGER," + Recordings.COLUMN_STATE + " TEXT NOT NULL)"; + private static final String SQL_CREATE_DVR_CHANNELS = + "CREATE TABLE " + DvrChannels.TABLE_NAME + "(" + + DvrChannels._ID + " INTEGER PRIMARY KEY AUTOINCREMENT)"; + + private static final String SQL_CREATE_DVR_PROGRAMS = + "CREATE TABLE " + DvrPrograms.TABLE_NAME + "(" + + DvrPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT)"; + + private static final String SQL_CREATE_RECORDING_PROGRAMS = + "CREATE TABLE " + RecordingToPrograms.TABLE_NAME + "(" + + RecordingToPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + RecordingToPrograms.COLUMN_RECORDING_ID + " INTEGER," + + RecordingToPrograms.COLUMN_PROGRAM_ID + " INTEGER," + + "FOREIGN KEY(" + RecordingToPrograms.COLUMN_RECORDING_ID + + ") REFERENCES " + Recordings.TABLE_NAME + "(" + Recordings._ID + + ") ON UPDATE CASCADE ON DELETE CASCADE," + + "FOREIGN KEY(" + RecordingToPrograms.COLUMN_PROGRAM_ID + + ") REFERENCES " + DvrPrograms.TABLE_NAME + "(" + DvrPrograms._ID + + ") ON UPDATE CASCADE ON DELETE CASCADE)"; + private static final String SQL_DROP_RECORDINGS = "DROP TABLE IF EXISTS " + Recordings.TABLE_NAME; + private static final String SQL_DROP_DVR_CHANNELS = "DROP TABLE IF EXISTS " + + DvrChannels.TABLE_NAME; + private static final String SQL_DROP_DVR_PROGRAMS = "DROP TABLE IF EXISTS " + + DvrPrograms.TABLE_NAME; + private static final String SQL_DROP_RECORDING_PROGRAMS = "DROP TABLE IF EXISTS " + + RecordingToPrograms.TABLE_NAME; public static final String WHERE_RECORDING_ID_EQUALS = Recordings._ID + " = ?"; public DvrDatabaseHelper(Context context) { @@ -68,10 +99,22 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { public void onCreate(SQLiteDatabase db) { if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_RECORDINGS); db.execSQL(SQL_CREATE_RECORDINGS); + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_DVR_CHANNELS); + db.execSQL(SQL_CREATE_DVR_CHANNELS); + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_DVR_PROGRAMS); + db.execSQL(SQL_CREATE_DVR_PROGRAMS); + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_RECORDING_PROGRAMS); + db.execSQL(SQL_CREATE_RECORDING_PROGRAMS); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_RECORDING_PROGRAMS); + db.execSQL(SQL_DROP_RECORDING_PROGRAMS); + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_DVR_PROGRAMS); + db.execSQL(SQL_DROP_DVR_PROGRAMS); + if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_DVR_CHANNELS); + db.execSQL(SQL_DROP_DVR_CHANNELS); if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_RECORDINGS); db.execSQL(SQL_DROP_RECORDINGS); onCreate(db); @@ -92,15 +135,15 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { * * @return The list of recordings with id set. The id will be -1 if there was an error. */ - public List<ScheduledRecording> insertRecordings(ScheduledRecording... scheduledRecordings) { - updateChannelsFromRecordings(scheduledRecordings); + public List<Recording> insertRecordings(Recording... recordings) { + updateChannelsFromRecordings(recordings); SQLiteDatabase db = getReadableDatabase(); - List<ScheduledRecording> results = new ArrayList<>(); - for (ScheduledRecording r : scheduledRecordings) { - ContentValues values = ScheduledRecording.toContentValues(r); + List<Recording> results = new ArrayList<>(); + for (Recording r : recordings) { + ContentValues values = getContentValues(r); long id = db.insert(Recordings.TABLE_NAME, null, values); - results.add(ScheduledRecording.buildFrom(r).setId(id).build()); + results.add(Recording.buildFrom(r).setId(id).build()); } return results; } @@ -111,12 +154,13 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { * @return The list of row update counts. The count will be -1 if there was an error or 0 * if no match was found. The count is expected to be exactly 1 for each recording. */ - public List<Integer> updateRecordings(ScheduledRecording[] scheduledRecordings) { - updateChannelsFromRecordings(scheduledRecordings); + public List<Integer> updateRecordings(Recording[] recordings) { + updateChannelsFromRecordings(recordings); SQLiteDatabase db = getWritableDatabase(); List<Integer> results = new ArrayList<>(); - for (ScheduledRecording r : scheduledRecordings) { - ContentValues values = ScheduledRecording.toContentValues(r); + long count = 0; + for (Recording r : recordings) { + ContentValues values = getContentValues(r); int updated = db.update(Recordings.TABLE_NAME, values, Recordings._ID + " = ?", new String[] {String.valueOf(r.getId())}); results.add(updated); @@ -124,21 +168,42 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { return results; } - private void updateChannelsFromRecordings(ScheduledRecording[] scheduledRecordings) { + private void updateChannelsFromRecordings(Recording[] recordings) { // TODO(DVR) implement/ // TODO(DVR) consider not deleting channels instead of keeping a separate table. } + private ContentValues getContentValues(Recording r) { + ContentValues values = new ContentValues(); + // TODO(DVR): use DVR channel id instead + Channel channel = r.getChannel(); + if (channel != null) { + values.put(Recordings.COLUMN_CHANNEL_ID, channel.getId()); + } + values.put(Recordings.COLUMN_PRIORITY, r.getPriority()); + values.put(Recordings.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs()); + values.put(Recordings.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs()); + values.put(Recordings.COLUMN_STATE, r.getState()); + values.put(Recordings.COLUMN_MEDIA_SIZE, r.getSize()); + values.put(Recordings.COLUMN_TYPE, r.getType()); + if (r.getUri() != null) { + values.put(Recordings.COLUMN_URI, r.getUri().toString()); + } + return values; + } + /** * Delete recordings. * * @return The list of row update counts. The count will be -1 if there was an error or 0 * if no match was found. The count is expected to be exactly 1 for each recording. */ - public List<Integer> deleteRecordings(ScheduledRecording[] scheduledRecordings) { + public List<Integer> deleteRecordings(Recording[] recordings) { SQLiteDatabase db = getWritableDatabase(); List<Integer> results = new ArrayList<>(); - for (ScheduledRecording r : scheduledRecordings) { + long count = 0; + for (Recording r : recordings) { + ContentValues values = getContentValues(r); int deleted = db.delete(Recordings.TABLE_NAME, WHERE_RECORDING_ID_EQUALS, new String[] {String.valueOf(r.getId())}); results.add(deleted); diff --git a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/DvrBrowseFragment.java index 70e71cab..87e47930 100644 --- a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java +++ b/src/com/android/tv/dvr/ui/DvrBrowseFragment.java @@ -20,33 +20,26 @@ import android.os.Bundle; import android.support.annotation.IntDef; import android.support.v17.leanback.app.BrowseFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; -import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; -import android.support.v17.leanback.widget.ObjectAdapter; import android.util.Log; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.common.recording.RecordedProgram; import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.Recording; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.LinkedHashMap; +import java.util.List; /** * {@link BrowseFragment} for DVR functions. */ public class DvrBrowseFragment extends BrowseFragment { private static final String TAG = "DvrBrowseFragment"; - private static final boolean DEBUG = false; - - private ScheduledRecordingsAdapter mRecordingsInProgressAdapter; - private ScheduledRecordingsAdapter mRecordingsNotStatedAdapter; - private RecordedProgramsAdapter mRecordedProgramsAdapter; @IntDef({DVR_CURRENT_RECORDINGS, DVR_SCHEDULED_RECORDINGS, DVR_RECORDED_PROGRAMS, DVR_SETTINGS}) @Retention(RetentionPolicy.SOURCE) @@ -56,48 +49,27 @@ public class DvrBrowseFragment extends BrowseFragment { public static final int DVR_RECORDED_PROGRAMS = 2; public static final int DVR_SETTINGS = 3; - private static final LinkedHashMap<Integer, Integer> sHeaders = + private static LinkedHashMap<Integer, Integer> sHeaders = new LinkedHashMap<Integer, Integer>() {{ put(DVR_CURRENT_RECORDINGS, R.string.dvr_main_current_recordings); put(DVR_SCHEDULED_RECORDINGS, R.string.dvr_main_scheduled_recordings); put(DVR_RECORDED_PROGRAMS, R.string.dvr_main_recorded_programs); - /* put(DVR_SETTINGS, R.string.dvr_main_settings); */ // TODO: Temporarily remove it for DP. + put(DVR_SETTINGS, R.string.dvr_main_settings); }}; private DvrDataManager mDvrDataManager; private ArrayObjectAdapter mRowsAdapter; @Override - public void onCreate(Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager(); + public void onActivityCreated(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onActivityCreated(savedInstanceState); setupUiElements(); - setupAdapters(); - mRecordingsInProgressAdapter.start(); - mRecordingsNotStatedAdapter.start(); - mRecordedProgramsAdapter.start(); - initRows(); + setupAdapter(); prepareEntranceTransition(); - startEntranceTransition(); - } - @Override - public void onStart() { - if (DEBUG) Log.d(TAG, "onStart"); - super.onStart(); - // TODO: It's a workaround for a bug that a progress bar isn't hidden. - // We need to remove it later. - getProgressBarManager().disableProgressBar(); - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy"); - mRecordingsInProgressAdapter.stop(); - mRecordingsNotStatedAdapter.stop(); - mRecordedProgramsAdapter.stop(); - super.onDestroy(); + // TODO: load asynchronously. + loadData(); } private void setupUiElements() { @@ -105,51 +77,43 @@ public class DvrBrowseFragment extends BrowseFragment { setHeadersTransitionOnBackEnabled(false); } - private void setupAdapters() { + private void setupAdapter() { + mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager(); mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); setAdapter(mRowsAdapter); - ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); - EmptyItemPresenter emptyItemPresenter = new EmptyItemPresenter(this); - ScheduledRecordingPresenter scheduledRecordingPresenter = new ScheduledRecordingPresenter( - getContext()); - RecordedProgramPresenter recordedProgramPresenter = new RecordedProgramPresenter( - getContext()); - presenterSelector.addClassPresenter(ScheduledRecording.class, scheduledRecordingPresenter); - presenterSelector.addClassPresenter(RecordedProgram.class, recordedProgramPresenter); - presenterSelector.addClassPresenter(EmptyHolder.class, emptyItemPresenter); - mRecordingsInProgressAdapter = new ScheduledRecordingsAdapter(mDvrDataManager, - ScheduledRecording.STATE_RECORDING_IN_PROGRESS, presenterSelector); - mRecordingsNotStatedAdapter = new ScheduledRecordingsAdapter(mDvrDataManager, - ScheduledRecording.STATE_RECORDING_NOT_STARTED, presenterSelector); - mRecordedProgramsAdapter = new RecordedProgramsAdapter(mDvrDataManager, presenterSelector); } - private void initRows() { - mRowsAdapter.clear(); + private void loadRow(ArrayObjectAdapter gridRowAdapter, List<Recording> recordings) { + if (recordings == null || recordings.size() == 0) { + gridRowAdapter.add(null); + return; + } + for (Recording r : recordings) { + gridRowAdapter.add(r); + } + } + + private void loadData() { for (@DVR_HEADERS_MODE int i : sHeaders.keySet()) { HeaderItem gridHeader = new HeaderItem(i, getContext().getString(sHeaders.get(i))); - ObjectAdapter gridRowAdapter = null; + GridItemPresenter gridPresenter = new GridItemPresenter(this); + ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter); switch (i) { - case DVR_CURRENT_RECORDINGS: { - gridRowAdapter = mRecordingsInProgressAdapter; + case DVR_CURRENT_RECORDINGS: + loadRow(gridRowAdapter, mDvrDataManager.getStartedRecordings()); break; - } - case DVR_SCHEDULED_RECORDINGS: { - gridRowAdapter = mRecordingsNotStatedAdapter; - } + case DVR_SCHEDULED_RECORDINGS: + loadRow(gridRowAdapter, mDvrDataManager.getScheduledRecordings()); break; - case DVR_RECORDED_PROGRAMS: { - gridRowAdapter = mRecordedProgramsAdapter; - } + case DVR_RECORDED_PROGRAMS: + loadRow(gridRowAdapter, mDvrDataManager.getFinishedRecordings()); break; case DVR_SETTINGS: - gridRowAdapter = new ArrayObjectAdapter(new EmptyItemPresenter(this)); // TODO: provide setup rows. break; } - if (gridRowAdapter != null) { - mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter)); - } + mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter)); } + startEntranceTransition(); } } diff --git a/src/com/android/tv/dvr/ui/DvrDialogFragment.java b/src/com/android/tv/dvr/ui/DvrDialogFragment.java deleted file mode 100644 index 38de9d8d..00000000 --- a/src/com/android/tv/dvr/ui/DvrDialogFragment.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.app.FragmentManager; -import android.content.Context; -import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.guide.ProgramGuide; - -public class DvrDialogFragment extends HalfSizedDialogFragment { - private final DvrGuidedStepFragment mDvrGuidedStepFragment; - - public DvrDialogFragment(DvrGuidedStepFragment dvrGuidedStepFragment) { - mDvrGuidedStepFragment = dvrGuidedStepFragment; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - ProgramGuide programGuide = - ((MainActivity) getActivity()).getOverlayManager().getProgramGuide(); - if (programGuide != null && programGuide.isActive()) { - programGuide.cancelHide(); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - FragmentManager fm = getChildFragmentManager(); - GuidedStepFragment.add(fm, mDvrGuidedStepFragment, R.id.halfsized_dialog_host); - return view; - } - - @Override - public void onDetach() { - super.onDetach(); - ProgramGuide programGuide = - ((MainActivity) getActivity()).getOverlayManager().getProgramGuide(); - if (programGuide != null && programGuide.isActive()) { - programGuide.scheduleHide(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java deleted file mode 100644 index 0854b91a..00000000 --- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidanceStylist; -import android.support.v17.leanback.widget.VerticalGridView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.MainActivity; -import com.android.tv.TvApplication; -import com.android.tv.dialog.SafeDismissDialogFragment; -import com.android.tv.dvr.DvrManager; -import com.android.tv.guide.ProgramManager.TableEntry; -import com.android.tv.R; - -public class DvrGuidedStepFragment extends GuidedStepFragment { - private final TableEntry mEntry; - private DvrManager mDvrManager; - - public DvrGuidedStepFragment(TableEntry entry) { - mEntry = entry; - } - - protected TableEntry getEntry() { - return mEntry; - } - - protected DvrManager getDvrManager() { - return mDvrManager; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView(); - gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE); - return view; - } - - @Override - public GuidanceStylist onCreateGuidanceStylist() { - // Workaround: b/28448653 - return new GuidanceStylist() { - @Override - public int onProvideLayoutId() { - return R.layout.halfsized_guidance; - } - }; - } - - @Override - public int onProvideTheme() { - return R.style.Theme_TV_Dvr_GuidedStep; - } - - protected void dismissDialog() { - SafeDismissDialogFragment currentDialog = - ((MainActivity) getActivity()).getOverlayManager().getCurrentDialog(); - if (currentDialog instanceof DvrDialogFragment) { - currentDialog.dismiss(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrRecordConflictFragment.java b/src/com/android/tv/dvr/ui/DvrRecordConflictFragment.java deleted file mode 100644 index 92052b5b..00000000 --- a/src/com/android/tv/dvr/ui/DvrRecordConflictFragment.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import com.android.tv.MainActivity; -import com.android.tv.R; - -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; - -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.guide.ProgramManager.TableEntry; - -import java.text.DateFormat; -import java.util.Date; -import java.util.List; - -public class DvrRecordConflictFragment extends DvrGuidedStepFragment { - private static final int DVR_EPG_RECORD = 1; - private static final int DVR_EPG_NOT_RECORD = 2; - - private List<ScheduledRecording> mConflicts; - - public DvrRecordConflictFragment(TableEntry entry) { - super(entry); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mConflicts = getDvrManager().getScheduledRecordingsThatConflict(getEntry().program); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - final MainActivity tvActivity = (MainActivity) getActivity(); - final ChannelDataManager channelDataManager = tvActivity.getChannelDataManager(); - StringBuilder sb = new StringBuilder(); - for (ScheduledRecording r : mConflicts) { - Channel channel = channelDataManager.getChannel(r.getChannelId()); - if (channel == null) { - continue; - } - sb.append(channel.getDisplayName()) - .append(" : ") - .append(DateFormat.getDateTimeInstance().format(new Date(r.getStartTimeMs()))) - .append("\n"); - } - String title = getResources().getString(R.string.dvr_epg_conflict_dialog_title); - String description = sb.toString(); - return new Guidance(title, description, null, null); - } - - @Override - public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { - Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(DVR_EPG_RECORD) - .title(getResources().getString(R.string.dvr_epg_record)) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(DVR_EPG_NOT_RECORD) - .title(getResources().getString(R.string.dvr_epg_do_not_record)) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - Program program = getEntry().program; - if (action.getId() == DVR_EPG_RECORD) { - getDvrManager().addSchedule(program, mConflicts); - } - dismissDialog(); - } -} diff --git a/src/com/android/tv/dvr/ui/DvrRecordDeleteFragment.java b/src/com/android/tv/dvr/ui/DvrRecordDeleteFragment.java deleted file mode 100644 index d4d5cc41..00000000 --- a/src/com/android/tv/dvr/ui/DvrRecordDeleteFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.os.Bundle; - -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; - -import com.android.tv.R; -import com.android.tv.guide.ProgramManager.TableEntry; - -import java.util.List; - -public class DvrRecordDeleteFragment extends DvrGuidedStepFragment { - private static final int ACTION_DELETE_YES = 1; - private static final int ACTION_DELETE_NO = 2; - - public DvrRecordDeleteFragment(TableEntry entry) { - super(entry); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString(R.string.epg_dvr_dialog_message_delete_schedule); - return new Guidance(title, null, null, null); - } - - @Override - public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { - Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_DELETE_YES) - .title(getResources().getString(android.R.string.yes)) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_DELETE_NO) - .title(getResources().getString(android.R.string.no)) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - if (action.getId() == ACTION_DELETE_YES) { - getDvrManager().removeScheduledRecording(getEntry().scheduledRecording); - } - dismissDialog(); - } -} diff --git a/src/com/android/tv/dvr/ui/DvrRecordScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrRecordScheduleFragment.java deleted file mode 100644 index 77e78ccc..00000000 --- a/src/com/android/tv/dvr/ui/DvrRecordScheduleFragment.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.app.FragmentManager; -import android.os.Bundle; - -import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; - -import com.android.tv.data.Program; -import com.android.tv.dialog.SafeDismissDialogFragment; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.guide.ProgramManager.TableEntry; -import com.android.tv.MainActivity; -import com.android.tv.R; - -import java.util.List; - -public class DvrRecordScheduleFragment extends DvrGuidedStepFragment { - private static final int ACTION_RECORD_YES = 1; - private static final int ACTION_RECORD_NO = 2; - - public DvrRecordScheduleFragment(TableEntry entry) { - super(entry); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString(R.string.epg_dvr_dialog_message_schedule_recording); - return new Guidance(title, null, null, null); - } - - @Override - public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { - Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_RECORD_YES) - .title(getResources().getString(android.R.string.yes)) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_RECORD_NO) - .title(getResources().getString(android.R.string.no)) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - TableEntry entry = getEntry(); - Program program = entry.program; - final List<ScheduledRecording> conflicts = - getDvrManager().getScheduledRecordingsThatConflict(program); - if (action.getId() == ACTION_RECORD_YES) { - if (conflicts.isEmpty()) { - getDvrManager().addSchedule(program, conflicts); - dismissDialog(); - } else { - DvrRecordConflictFragment dvrConflict = new DvrRecordConflictFragment(entry); - SafeDismissDialogFragment currentDialog = - ((MainActivity) getActivity()).getOverlayManager().getCurrentDialog(); - if (currentDialog instanceof DvrDialogFragment) { - FragmentManager fm = currentDialog.getChildFragmentManager(); - GuidedStepFragment.add(fm, dvrConflict, R.id.halfsized_dialog_host); - } - } - } else if (action.getId() == ACTION_RECORD_NO) { - dismissDialog(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/EmptyHolder.java b/src/com/android/tv/dvr/ui/EmptyHolder.java deleted file mode 100644 index 45cd3a36..00000000 --- a/src/com/android/tv/dvr/ui/EmptyHolder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -/** - * Special object meaning a row is empty; - */ -final class EmptyHolder { - static final EmptyHolder EMPTY_HOLDER = new EmptyHolder(); - - private EmptyHolder() { - } -} diff --git a/src/com/android/tv/dvr/ui/EmptyItemPresenter.java b/src/com/android/tv/dvr/ui/EmptyItemPresenter.java deleted file mode 100644 index c0305128..00000000 --- a/src/com/android/tv/dvr/ui/EmptyItemPresenter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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.android.tv.dvr.ui; - -import android.content.res.Resources; -import android.graphics.Color; -import android.support.v17.leanback.widget.Presenter; -import android.view.Gravity; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.util.Utils; - -/** - * Shows the item "NONE". Used for rows with now items. - */ -public class EmptyItemPresenter extends Presenter { - - private final DvrBrowseFragment mMainFragment; - - public EmptyItemPresenter(DvrBrowseFragment mainFragment) { - mMainFragment = mainFragment; - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - TextView view = new TextView(parent.getContext()); - Resources resources = view.getResources(); - view.setLayoutParams(new ViewGroup.LayoutParams( - resources.getDimensionPixelSize(R.dimen.dvr_card_layout_width), - resources.getDimensionPixelSize(R.dimen.dvr_card_layout_width))); - view.setFocusable(true); - view.setFocusableInTouchMode(true); - view.setBackgroundColor( - Utils.getColor(mMainFragment.getResources(), R.color.setup_background)); - view.setTextColor(Color.WHITE); - view.setGravity(Gravity.CENTER); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, Object recording) { - ((TextView) viewHolder.view).setText( - viewHolder.view.getContext().getString(R.string.dvr_msg_no_recording_on_the_row)); - } - - @Override - public void onUnbindViewHolder(ViewHolder viewHolder) { } -} diff --git a/src/com/android/tv/dvr/ui/GridItemPresenter.java b/src/com/android/tv/dvr/ui/GridItemPresenter.java new file mode 100644 index 00000000..099816d4 --- /dev/null +++ b/src/com/android/tv/dvr/ui/GridItemPresenter.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 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.android.tv.dvr.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.support.v17.leanback.widget.Presenter; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.tv.MainActivity; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.Channel; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.Recording; +import com.android.tv.util.Utils; + +import java.util.List; + +public class GridItemPresenter extends Presenter { + private static final int GRID_ITEM_WIDTH = 200; + private static final int GRID_ITEM_HEIGHT = 200; + + private final DvrBrowseFragment mainFragment; + + public GridItemPresenter(DvrBrowseFragment mainFragment) { + this.mainFragment = mainFragment; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent) { + TextView view = new TextView(parent.getContext()); + view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT)); + view.setFocusable(true); + view.setFocusableInTouchMode(true); + view.setBackgroundColor( + Utils.getColor(mainFragment.getResources(), R.color.setup_background)); + view.setTextColor(Color.WHITE); + view.setGravity(Gravity.CENTER); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, Object recording) { + if (recording == null) { + ((TextView) viewHolder.view).setText(viewHolder.view.getContext() + .getString(R.string.dvr_msg_no_recording_on_the_row)); + } else { + final Recording r = (Recording) recording; + StringBuilder sb = new StringBuilder(); + List<Program> programs = r.getPrograms(); + if (programs != null && programs.size() > 0) { + sb.append(programs.get(0).getTitle()); + } else { + sb.append(viewHolder.view.getContext() + .getString(R.string.dvr_msg_program_title_unknown)); + } + sb.append(" "); + Channel channel = r.getChannel(); + if (channel != null) { + sb.append(channel.getDisplayName()); + } else { + sb.append(viewHolder.view.getContext().getString(R.string.dvr_msg_channel_unknown)); + } + sb.append(" ").append(Utils.toIsoDateTimeString(r.getStartTimeMs())); + ((TextView) viewHolder.view).setText(sb.toString()); + final Context context = viewHolder.view.getContext(); + viewHolder.view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switch (r.getState()) { + case Recording.STATE_RECORDING_NOT_STARTED: { + new AlertDialog.Builder(context) + .setNegativeButton(R.string.dvr_detail_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Toast.makeText(context, "Not implemented yet", + Toast.LENGTH_SHORT).show(); + } + }) + .show(); + break; + } + case Recording.STATE_RECORDING_IN_PROGRESS: { + new AlertDialog.Builder(context) + .setNegativeButton(R.string.dvr_detail_stop_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Toast.makeText(context, "Not implemented yet", + Toast.LENGTH_SHORT).show(); + } + }) + .setPositiveButton(R.string.dvr_detail_stop_keep, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Toast.makeText(context, "Not implemented yet", + Toast.LENGTH_SHORT).show(); + } + }) + .show(); + break; + } + case Recording.STATE_RECORDING_FINISHED: { + new AlertDialog.Builder(context) + .setNegativeButton(R.string.dvr_detail_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + DvrManager dvrManager = TvApplication + .getSingletons(mainFragment.getContext()) + .getDvrManager(); + // TODO(DVR) handle success/failure. + dvrManager.removeRecording(r); + } + }) + .setPositiveButton(R.string.dvr_detail_play, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(Utils.EXTRA_KEY_RECORDING_URI, + r.getUri()); + context.startActivity(intent); + ((Activity) context).finish(); + } + }) + .show(); + break; + } + } + } + }); + } + } + + @Override + public void onUnbindViewHolder(ViewHolder viewHolder) { + } +}
\ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java deleted file mode 100644 index dc89a8e0..00000000 --- a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.android.tv.dvr.ui; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.dialog.SafeDismissDialogFragment; -import com.android.tv.R; - -public class HalfSizedDialogFragment extends SafeDismissDialogFragment { - public static final String DIALOG_TAG = HalfSizedDialogFragment.class.getSimpleName(); - public static final String TRACKER_LABEL = "Half sized dialog"; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.halfsized_dialog, null); - } - - @Override - public int getTheme() { - return R.style.Theme_TV_dialog_HalfSizedDialog; - } - - @Override - public String getTrackerLabel() { - return TRACKER_LABEL; - } -} diff --git a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java deleted file mode 100644 index 0b656bdc..00000000 --- a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.media.tv.TvContract; -import android.support.v17.leanback.widget.Presenter; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; - -import java.util.List; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.recording.RecordedProgram; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.ui.DialogUtils; -import com.android.tv.util.Utils; - -/** - * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. - */ -public class RecordedProgramPresenter extends Presenter { - private final ChannelDataManager mChannelDataManager; - - public RecordedProgramPresenter(Context context) { - mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Context context = parent.getContext(); - RecordingCardView view = new RecordingCardView(context); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, Object o) { - final RecordedProgram recording = (RecordedProgram) o; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - final Context context = viewHolder.view.getContext(); - final Resources resources = context.getResources(); - - Channel channel = mChannelDataManager.getChannel(recording.getChannelId()); - - if (!TextUtils.isEmpty(recording.getTitle())) { - cardView.setTitle(recording.getTitle()); - } else { - cardView.setTitle(resources.getString(R.string.dvr_msg_program_title_unknown)); - } - if (recording.getPosterArt() != null) { - cardView.setImageUri(recording.getPosterArt()); - } else if (recording.getThumbnail() != null) { - cardView.setImageUri(recording.getThumbnail()); - } else { - if (channel != null) { - cardView.setImageUri(TvContract.buildChannelLogoUri(channel.getId()).toString()); - } - } - cardView.setContent(Utils.getDurationString(context, recording.getStartTimeUtcMillis(), - recording.getEndTimeUtcMillis(), true)); - //TODO: replace with a detail card - viewHolder.view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - DialogUtils.showListDialog(v.getContext(), - new int[] { R.string.dvr_detail_play, R.string.dvr_detail_delete }, - new Runnable[] { - new Runnable() { - @Override - public void run() { - Intent intent = new Intent(context, MainActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDING_URI, - recording.getUri()); - context.startActivity(intent); - ((Activity) context).finish(); - } - }, - new Runnable() { - @Override - public void run() { - DvrManager dvrManager = TvApplication - .getSingletons(context).getDvrManager(); - dvrManager.removeRecordedProgram(recording); - } - }, - }); - } - }); - - } - - @Override - public void onUnbindViewHolder(ViewHolder viewHolder) { - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - cardView.reset(); - } -} diff --git a/src/com/android/tv/dvr/ui/RecordedProgramsAdapter.java b/src/com/android/tv/dvr/ui/RecordedProgramsAdapter.java deleted file mode 100644 index eeb26041..00000000 --- a/src/com/android/tv/dvr/ui/RecordedProgramsAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.support.v17.leanback.widget.PresenterSelector; - -import com.android.tv.common.recording.RecordedProgram; -import com.android.tv.dvr.DvrDataManager; - -/** - * Adapter for {@link RecordedProgram}. - */ -final class RecordedProgramsAdapter extends SortedArrayAdapter<RecordedProgram> - implements DvrDataManager.RecordedProgramListener { - private final DvrDataManager mDataManager; - - RecordedProgramsAdapter(DvrDataManager dataManager, PresenterSelector presenterSelector) { - super(presenterSelector, RecordedProgram.START_TIME_THEN_ID_COMPARATOR); - mDataManager = dataManager; - } - - public void start() { - clear(); - addAll(mDataManager.getRecordedPrograms()); - mDataManager.addRecordedProgramListener(this); - } - - public void stop() { - mDataManager.removeRecordedProgramListener(this); - } - - @Override - long getId(RecordedProgram item) { - return item.getId(); - } - - @Override // DvrDataManager.RecordedProgramListener - public void onRecordedProgramAdded(RecordedProgram recordedProgram) { - add(recordedProgram); - } - - @Override // DvrDataManager.RecordedProgramListener - public void onRecordedProgramChanged(RecordedProgram recordedProgram) { - change(recordedProgram); - } - - @Override // DvrDataManager.RecordedProgramListener - public void onRecordedProgramRemoved(RecordedProgram recordedProgram) { - remove(recordedProgram); - } -} diff --git a/src/com/android/tv/dvr/ui/RecordingCardView.java b/src/com/android/tv/dvr/ui/RecordingCardView.java deleted file mode 100644 index def11248..00000000 --- a/src/com/android/tv/dvr/ui/RecordingCardView.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.support.v17.leanback.widget.BaseCardView; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.util.ImageLoader; - -/** - * A CardView for displaying info about a {@link com.android.tv.dvr.ScheduledRecording} or - * {@link com.android.tv.common.recording.RecordedProgram} - */ -class RecordingCardView extends BaseCardView { - private final ImageView mImageView; - private final int mImageWidth; - private final int mImageHeight; - private String mImageUri; - private final TextView mTitleView; - private final TextView mContentView; - private final Drawable mDefaultImage; - - RecordingCardView(Context context) { - super(context); - //TODO(dvr): move these to the layout XML. - setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA); - setFocusable(true); - setFocusableInTouchMode(true); - mDefaultImage = getResources().getDrawable(R.drawable.default_now_card, null); - - LayoutInflater inflater = LayoutInflater.from(getContext()); - inflater.inflate(R.layout.dvr_recording_card_view, this); - - mImageView = (ImageView) findViewById(R.id.image); - mImageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width); - mImageHeight = getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width); - mTitleView = (TextView) findViewById(R.id.title); - mContentView = (TextView) findViewById(R.id.content); - } - - void setTitle(CharSequence title) { - mTitleView.setText(title); - } - - void setContent(CharSequence content) { - mContentView.setText(content); - } - - void setImageUri(String uri) { - mImageUri = uri; - if (TextUtils.isEmpty(uri)) { - mImageView.setImageDrawable(mDefaultImage); - } else { - ImageLoader.loadBitmap(getContext(), uri, mImageWidth, mImageHeight, - new RecordingCardImageLoaderCallback(this, uri)); - } - } - - public void setImageUri(Uri uri) { - if (uri != null) { - setImageUri(uri.toString()); - } else { - setImageUri(""); - } - } - - private static class RecordingCardImageLoaderCallback - extends ImageLoader.ImageLoaderCallback<RecordingCardView> { - private final String mUri; - - RecordingCardImageLoaderCallback(RecordingCardView referent, String uri) { - super(referent); - mUri = uri; - } - - @Override - public void onBitmapLoaded(RecordingCardView view, @Nullable Bitmap bitmap) { - if (bitmap == null || !mUri.equals(view.mImageUri)) { - view.mImageView.setImageDrawable(view.mDefaultImage); - } else { - view.mImageView.setImageDrawable(new BitmapDrawable(view.getResources(), bitmap)); - } - } - } - - public void reset() { - mTitleView.setText(""); - mContentView.setText(""); - mImageView.setImageDrawable(mDefaultImage); - } -} diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java deleted file mode 100644 index 533a4882..00000000 --- a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.media.tv.TvContract; -import android.support.annotation.Nullable; -import android.support.v17.leanback.widget.Presenter; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; -import com.android.tv.data.ProgramDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.util.Utils; - -/** - * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. - */ -public class ScheduledRecordingPresenter extends Presenter { - private final ChannelDataManager mChannelDataManager; - - private static final class ScheduledRecordingViewHolder extends ViewHolder { - private ProgramDataManager.QueryProgramTask mQueryProgramTask; - - ScheduledRecordingViewHolder(RecordingCardView view) { - super(view); - } - } - - public ScheduledRecordingPresenter(Context context) { - ApplicationSingletons singletons = TvApplication.getSingletons(context); - mChannelDataManager = singletons.getChannelDataManager(); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Context context = parent.getContext(); - RecordingCardView view = new RecordingCardView(context); - return new ScheduledRecordingViewHolder(view); - } - - @Override - public void onBindViewHolder(ViewHolder baseHolder, Object o) { - ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - final ScheduledRecording recording = (ScheduledRecording) o; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - final Context context = viewHolder.view.getContext(); - - long programId = recording.getProgramId(); - if (programId == ScheduledRecording.ID_NOT_SET) { - setTitleAndImage(cardView, recording, null); - } else { - viewHolder.mQueryProgramTask = new ProgramDataManager.QueryProgramTask( - context.getContentResolver(), programId) { - @Override - protected void onPostExecute(Program program) { - super.onPostExecute(program); - setTitleAndImage(cardView, recording, program); - } - }; - viewHolder.mQueryProgramTask.executeOnDbThread(); - - } - cardView.setContent(Utils.getDurationString(context, recording.getStartTimeMs(), - recording.getEndTimeMs(), true)); - //TODO: replace with a detail card - View.OnClickListener clickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - switch (recording.getState()) { - case ScheduledRecording.STATE_RECORDING_NOT_STARTED: { - showScheduledRecordingDialog(v.getContext(), recording); - break; - } - case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: { - showCurrentlyRecordingDialog(v.getContext(), recording); - break; - } - } - } - }; - baseHolder.view.setOnClickListener(clickListener); - } - - private void setTitleAndImage(RecordingCardView cardView, ScheduledRecording recording, - @Nullable Program program) { - if (program != null) { - cardView.setTitle(program.getTitle()); - cardView.setImageUri(program.getPosterArtUri()); - } else { - cardView.setTitle( - cardView.getResources().getString(R.string.dvr_msg_program_title_unknown)); - Channel channel = mChannelDataManager.getChannel(recording.getChannelId()); - if (channel != null) { - cardView.setImageUri(TvContract.buildChannelLogoUri(channel.getId()).toString()); - } - } - } - - @Override - public void onUnbindViewHolder(ViewHolder baseHolder) { - ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - if (viewHolder.mQueryProgramTask != null) { - viewHolder.mQueryProgramTask.cancel(true); - viewHolder.mQueryProgramTask = null; - } - cardView.reset(); - } - - private void showScheduledRecordingDialog(final Context context, - final ScheduledRecording recording) { - DialogInterface.OnClickListener removeScheduleListener - = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // TODO(DVR) handle success/failure. - DvrManager dvrManager = TvApplication.getSingletons(context) - .getDvrManager(); - dvrManager.removeScheduledRecording((ScheduledRecording) recording); - } - }; - new AlertDialog.Builder(context) - .setMessage(R.string.epg_dvr_dialog_message_remove_recording_schedule) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, removeScheduleListener) - .show(); - } - - private void showCurrentlyRecordingDialog(final Context context, - final ScheduledRecording recording) { - DialogInterface.OnClickListener stopRecordingListener - = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - DvrManager dvrManager = TvApplication.getSingletons(context) - .getDvrManager(); - dvrManager.stopRecording((ScheduledRecording) recording); - } - }; - new AlertDialog.Builder(context) - .setMessage(R.string.epg_dvr_dialog_message_stop_recording) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, stopRecordingListener) - .show(); - } -} diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingsAdapter.java b/src/com/android/tv/dvr/ui/ScheduledRecordingsAdapter.java deleted file mode 100644 index 65955276..00000000 --- a/src/com/android/tv/dvr/ui/ScheduledRecordingsAdapter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.support.v17.leanback.widget.PresenterSelector; - -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.ScheduledRecording; - -/** - * Adapter for {@link ScheduledRecording} filtered by - * {@link com.android.tv.dvr.ScheduledRecording.RecordingState}. - */ -final class ScheduledRecordingsAdapter extends SortedArrayAdapter<ScheduledRecording> - implements DvrDataManager.ScheduledRecordingListener { - private final int mState; - private final DvrDataManager mDataManager; - - ScheduledRecordingsAdapter(DvrDataManager dataManager, int state, - PresenterSelector presenterSelector) { - super(presenterSelector, ScheduledRecording.START_TIME_THEN_PRIORITY_COMPARATOR); - mDataManager = dataManager; - mState = state; - } - - public void start() { - clear(); - switch (mState) { - case ScheduledRecording.STATE_RECORDING_NOT_STARTED: - addAll(mDataManager.getNonStartedScheduledRecordings()); - break; - case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: - addAll(mDataManager.getStartedRecordings()); - break; - default: - throw new IllegalStateException("Unknown recording state " + mState); - - } - mDataManager.addScheduledRecordingListener(this); - } - - public void stop() { - mDataManager.removeScheduledRecordingListener(this); - } - - @Override - long getId(ScheduledRecording item) { - return item.getId(); - } - - @Override //DvrDataManager.ScheduledRecordingListener - public void onScheduledRecordingAdded(ScheduledRecording scheduledRecording) { - if (scheduledRecording.getState() == mState) { - add(scheduledRecording); - } - } - - @Override //DvrDataManager.ScheduledRecordingListener - public void onScheduledRecordingRemoved(ScheduledRecording scheduledRecording) { - remove(scheduledRecording); - } - - @Override //DvrDataManager.ScheduledRecordingListener - public void onScheduledRecordingStatusChanged(ScheduledRecording scheduledRecording) { - if (scheduledRecording.getState() == mState) { - change(scheduledRecording); - } else { - remove(scheduledRecording); - } - } -} diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java deleted file mode 100644 index 8a8bcdeb..00000000 --- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 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.android.tv.dvr.ui; - -import android.support.v17.leanback.widget.ObjectAdapter; -import android.support.v17.leanback.widget.PresenterSelector; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Keeps a set of {@code T} items sorted, but leaving a {@link EmptyHolder} - * if there is no items. - * - * <p>{@code T} must have stable IDs. - */ -abstract class SortedArrayAdapter<T> extends ObjectAdapter { - private final List<T> mItems = new ArrayList<>(); - private final Comparator<T> mComparator; - - SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) { - super(presenterSelector); - mComparator = comparator; - setHasStableIds(true); - } - - @Override - public final int size() { - return mItems.isEmpty() ? 1 : mItems.size(); - } - - @Override - public final Object get(int position) { - return isEmpty() ? EmptyHolder.EMPTY_HOLDER : getItem(position); - } - - @Override - public final long getId(int position) { - if (isEmpty()) { - return NO_ID; - } - T item = mItems.get(position); - return item == null ? NO_ID : getId(item); - } - - /** - * Returns the id of the the given {@code item}. - * - * The id must be stable. - */ - abstract long getId(T item); - - /** - * Returns the item at the given {@code position}. - * - * @throws IndexOutOfBoundsException if the position is out of range - * (<tt>position < 0 || position >= size()</tt>) - */ - final T getItem(int position) { - return mItems.get(position); - } - - /** - * Returns {@code true} if the list of items is empty. - * - * <p><b>NOTE</b> when the item list is empty the adapter has a size of 1 and - * {@link EmptyHolder#EMPTY_HOLDER} at position 0; - */ - final boolean isEmpty() { - return mItems.isEmpty(); - } - - /** - * Removes all elements from the list. - * - * <p><b>NOTE</b> when the item list is empty the adapter has a size of 1 and - * {@link EmptyHolder#EMPTY_HOLDER} at position 0; - */ - final void clear() { - mItems.clear(); - notifyChanged(); - } - - /** - * Adds the objects in the given collection to the adapter keeping the elements sorted. - * If the index is >= {@link #size} an exception will be thrown. - * - * @param items A {@link Collection} of items to insert. - */ - final void addAll(Collection<T> items) { - mItems.addAll(items); - Collections.sort(mItems, mComparator); - notifyChanged(); - } - - /** - * Adds an item in sorted order to the adapter. - * - * @param item The item to add in sorted order to the adapter. - */ - final void add(T item) { - int i = findWhereToInsert(item); - mItems.add(i, item); - if (mItems.size() == 1) { - notifyItemRangeChanged(0, 1); - } else { - notifyItemRangeInserted(i, 1); - } - } - - /** - * Remove an item from the list - * - * @param item The item to remove from the adapter. - */ - final void remove(T item) { - int index = indexOf(item); - if (index != -1) { - mItems.remove(index); - if (mItems.isEmpty()) { - notifyItemRangeChanged(0, 1); - } else { - notifyItemRangeRemoved(index, 1); - } - } - } - - /** - * Change an item in the list. - * @param item The item to change. - */ - final void change(T item) { - int oldIndex = indexOf(item); - if (oldIndex != -1) { - T old = mItems.get(oldIndex); - if (mComparator.compare(old, item) == 0) { - mItems.set(oldIndex, item); - notifyItemRangeChanged(oldIndex, 1); - return; - } - mItems.remove(oldIndex); - } - int newIndex = findWhereToInsert(item); - mItems.add(newIndex, item); - - if (oldIndex != -1) { - notifyItemRangeRemoved(oldIndex, 1); - } - if (newIndex != -1) { - notifyItemRangeInserted(newIndex, 1); - } - } - - private int indexOf(T item) { - long id = getId(item); - for (int i = 0; i < mItems.size(); i++) { - T r = mItems.get(i); - if (getId(r) == id) { - return i; - } - } - return -1; - } - - private int findWhereToInsert(T item) { - int i; - int size = mItems.size(); - for (i = 0; i < size; i++) { - T r = mItems.get(i); - if (mComparator.compare(r, item) > 0) { - return i; - } - } - return size; - } -} diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java index 77de5827..1339ddf8 100644 --- a/src/com/android/tv/guide/ProgramGrid.java +++ b/src/com/android/tv/guide/ProgramGrid.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.support.v17.leanback.widget.VerticalGridView; -import android.support.v7.widget.RecyclerView.LayoutManager; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -67,16 +66,6 @@ public class ProgramGrid extends VerticalGridView { } }; - private final ViewTreeObserver.OnPreDrawListener mPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getViewTreeObserver().removeOnPreDrawListener(this); - updateInputLogo(); - return true; - } - }; - private ProgramManager mProgramManager; private View mNextFocusByUpDown; @@ -94,7 +83,7 @@ public class ProgramGrid extends VerticalGridView { private boolean mKeepCurrentProgram; private ChildFocusListener mChildFocusListener; - private final OnRepeatedKeyInterceptListener mOnRepeatedKeyInterceptListener; + private OnRepeatedKeyInterceptListener mOnRepeatedKeyInterceptListener; interface ChildFocusListener { /** @@ -343,10 +332,6 @@ public class ProgramGrid extends VerticalGridView { return contains((View) v.getParent()); } - public void onItemSelectionReset() { - getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); - } - @Override public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { if (mLastFocusedView != null && mLastFocusedView.isShown()) { @@ -374,49 +359,6 @@ public class ProgramGrid extends VerticalGridView { int maxY = (mSelectionRow + 1) * mRowHeight + mDetailHeight; if (y > maxY) scrollBy(0, y - maxY); } - updateInputLogo(); - } - - @Override - public void onViewRemoved(View view) { - // It is required to ensure input logo showing when the scroll is moved to most bottom. - updateInputLogo(); - } - - private int getFirstVisibleChildIndex() { - final LayoutManager mLayoutManager = getLayoutManager(); - int top = mLayoutManager.getPaddingTop(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View childView = getChildAt(i); - int childTop = mLayoutManager.getDecoratedTop(childView); - int childBottom = mLayoutManager.getDecoratedBottom(childView); - if ((childTop + childBottom) / 2 > top) { - return i; - } - } - return -1; - } - - public void updateInputLogo() { - int childCount = getChildCount(); - if (childCount == 0) { - return; - } - int firstVisibleChildIndex = getFirstVisibleChildIndex(); - if (firstVisibleChildIndex == -1) { - return; - } - View childView = getChildAt(firstVisibleChildIndex); - int childAdapterPosition = getChildAdapterPosition(childView); - ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView)) - .updateInputLogo(childAdapterPosition, true); - for (int i = firstVisibleChildIndex + 1; i < childCount; i++) { - childView = getChildAt(i); - ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView)) - .updateInputLogo(childAdapterPosition, false); - childAdapterPosition = getChildAdapterPosition(childView); - } } private static void findFocusables(View v, ArrayList<View> outFocusable) { diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index bfcb8b0d..77a1146b 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -31,7 +31,6 @@ import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v17.leanback.widget.OnChildSelectedListener; import android.support.v17.leanback.widget.SearchOrbView; import android.support.v17.leanback.widget.VerticalGridView; @@ -53,7 +52,6 @@ import com.android.tv.common.WeakHandler; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.GenreItems; import com.android.tv.data.ProgramDataManager; -import com.android.tv.dvr.DvrDataManager; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -162,11 +160,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { public ProgramGuide(MainActivity activity, ChannelTuner channelTuner, TvInputManagerHelper tvInputManagerHelper, ChannelDataManager channelDataManager, - ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager, - Tracker tracker, Runnable preShowRunnable, Runnable postHideRunnable) { + ProgramDataManager programDataManager, Tracker tracker, Runnable preShowRunnable, + Runnable postHideRunnable) { mActivity = activity; - mProgramManager = new ProgramManager(tvInputManagerHelper, channelDataManager, - programDataManager, dvrDataManager); + mProgramManager = new ProgramManager(tvInputManagerHelper, + channelDataManager, + programDataManager); mChannelTuner = channelTuner; mTracker = tracker; mPreShowRunnable = preShowRunnable; @@ -246,7 +245,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mTimelineRow.setAdapter(mTimeListAdapter); ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, - mProgramManager, this); + tvInputManagerHelper, mProgramManager, this); programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { @@ -591,10 +590,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { return mTimelineRow.getScrollOffset(); } - /** - * Cancel hiding the program guide. - */ - public void cancelHide() { + private void cancelHide() { mHandler.removeCallbacks(mHideRunnable); } @@ -724,7 +720,6 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), 0)); mGrid.resetFocusState(); - mGrid.onItemSelectionReset(); mIsDuringResetRowSelection = false; } diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index 172ee070..09a93037 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -16,7 +16,9 @@ package com.android.tv.guide; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -24,7 +26,6 @@ import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Handler; import android.os.SystemClock; -import android.support.v4.os.BuildCompat; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -42,14 +43,15 @@ import com.android.tv.TvApplication; import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; +import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ui.DvrDialogFragment; -import com.android.tv.dvr.ui.DvrRecordDeleteFragment; -import com.android.tv.dvr.ui.DvrRecordScheduleFragment; +import com.android.tv.dvr.Recording; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.Utils; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; public class ProgramItemView extends TextView { @@ -58,6 +60,9 @@ public class ProgramItemView extends TextView { private static final long FOCUS_UPDATE_FREQUENCY = TimeUnit.SECONDS.toMillis(1); private static final int MAX_PROGRESS = 10000; // From android.widget.ProgressBar.MAX_VALUE + private static final int ACTION_RECORD_PROGRAM = 100; + private static final int ACTION_RECORD_SEASON = 101; + // State indicating the focused program is the current program private static final int[] STATE_CURRENT_PROGRAM = { R.attr.state_current_program }; @@ -84,10 +89,6 @@ public class ProgramItemView extends TextView { @Override public void onClick(final View view) { TableEntry entry = ((ProgramItemView) view).mTableEntry; - if (entry == null) { - //do nothing - return; - } ApplicationSingletons singletons = TvApplication.getSingletons(view.getContext()); Tracker tracker = singletons.getTracker(); tracker.sendEpgItemClicked(); @@ -104,38 +105,78 @@ public class ProgramItemView extends TextView { }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0 : view.getResources() .getInteger(R.integer.program_guide_ripple_anim_duration)); - } else if (CommonFeatures.DVR.isEnabled(view.getContext()) && BuildCompat - .isAtLeastN()) { + } else if (CommonFeatures.DVR.isEnabled(view.getContext())) { final MainActivity tvActivity = (MainActivity) view.getContext(); final DvrManager dvrManager = singletons.getDvrManager(); final Channel channel = tvActivity.getChannelDataManager() .getChannel(entry.channelId); - if (dvrManager.canRecord(channel.getInputId()) && entry.program != null) { - if (entry.scheduledRecording == null) { - showDvrDialog(view, entry); - } else { - showRecordDeleteDialog(view, entry); - } + if (dvrManager.canRecord(channel.getInputId())) { + showDvrDialog(view, entry, dvrManager); } } } - private void showDvrDialog(final View view, TableEntry entry) { - Utils.showToastMessageForDeveloperFeature(view.getContext()); - DvrRecordScheduleFragment dvrRecordScheduleFragment = - new DvrRecordScheduleFragment(entry); - DvrDialogFragment dvrDialogFragment = new DvrDialogFragment(dvrRecordScheduleFragment); - ((MainActivity) view.getContext()).getOverlayManager().showDialogFragment( - DvrDialogFragment.DIALOG_TAG, dvrDialogFragment, true, true); + private void showDvrDialog(final View view, TableEntry entry, final DvrManager dvrManager) { + List<CharSequence> items = new ArrayList<>(); + final List<Integer> actions = new ArrayList<>(); + // TODO: the items can be changed by the state of the program. For example, + // if the program is already added in scheduler, we need to show an item to + // delete the recording schedule. + items.add(view.getResources().getString(R.string.epg_dvr_record_program)); + actions.add(ACTION_RECORD_PROGRAM); + items.add(view.getResources().getString(R.string.epg_dvr_record_season)); + actions.add(ACTION_RECORD_SEASON); + + final Program program = entry.program; + final List<Recording> conflicts = dvrManager + .getScheduledRecordingsThatConflict(program); + // TODO: it is a tentative UI. Don't publish the UI. + DialogInterface.OnClickListener onClickListener + = new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int which) { + if (actions.get(which) == ACTION_RECORD_PROGRAM) { + if (conflicts.isEmpty()) { + dvrManager.addSchedule(program, conflicts); + } else { + showConflictDialog(view, dvrManager, program, conflicts); + } + } else if (actions.get(which) == ACTION_RECORD_SEASON) { + dvrManager.addSeasonSchedule(program); + } + dialog.dismiss(); + } + }; + new AlertDialog.Builder(view.getContext()) + .setItems(items.toArray(new CharSequence[items.size()]), onClickListener) + .create() + .show(); } + }; - private void showRecordDeleteDialog(final View view, final TableEntry entry) { - DvrRecordDeleteFragment recordDeleteDialogFragment = new DvrRecordDeleteFragment(entry); - DvrDialogFragment dvrDialogFragment = new DvrDialogFragment(recordDeleteDialogFragment); - ((MainActivity) view.getContext()).getOverlayManager().showDialogFragment( - DvrDialogFragment.DIALOG_TAG, dvrDialogFragment, true, true); + private static void showConflictDialog(final View view, final DvrManager dvrManager, + final Program program, final List<Recording> conflicts) { + DialogInterface.OnClickListener conflictClickListener + = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + dvrManager.addSchedule(program, conflicts); + dialog.dismiss(); + } + } + }; + StringBuilder sb = new StringBuilder(); + for (Recording r : conflicts) { + sb.append(r.toString()).append('\n'); } - }; + new AlertDialog.Builder(view.getContext()).setTitle(R.string.dvr_epg_conflict_dialog_title) + .setMessage(sb.toString()) + .setPositiveButton(R.string.dvr_epg_record, conflictClickListener) + .setNegativeButton(R.string.dvr_epg_do_not_record, conflictClickListener) + .create() + .show(); + } private static final View.OnFocusChangeListener ON_FOCUS_CHANGED = new View.OnFocusChangeListener() { @@ -157,10 +198,6 @@ public class ProgramItemView extends TextView { public void run() { refreshDrawableState(); TableEntry entry = mTableEntry; - if (entry == null) { - //do nothing - return; - } if (entry.isCurrentProgram()) { Drawable background = getBackground(); int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); @@ -183,8 +220,6 @@ public class ProgramItemView extends TextView { public ProgramItemView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - setOnClickListener(ON_CLICKED); - setOnFocusChangeListener(ON_FOCUS_CHANGED); } private void initIfNeeded() { @@ -247,9 +282,11 @@ public class ProgramItemView extends TextView { return mTableEntry; } - public void setValues(TableEntry entry, int selectedGenreId, long fromUtcMillis, - long toUtcMillis, String gapTitle) { + public void onBind(TableEntry entry, ProgramListAdapter adapter) { mTableEntry = entry; + setOnClickListener(ON_CLICKED); + setOnFocusChangeListener(ON_FOCUS_CHANGED); + ProgramManager programManager = adapter.getProgramManager(); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = entry.getWidth(); @@ -266,19 +303,16 @@ public class ProgramItemView extends TextView { setText(null); } else { if (entry.isGap()) { - title = gapTitle; + if (entry.isBlocked()) { + title = adapter.getBlockedProgramTitle(); + } else { + title = adapter.getNoInfoProgramTitle(); + } episode = null; - } else if (entry.hasGenre(selectedGenreId)) { + } else if (entry.hasGenre(programManager.getSelectedGenreId())) { titleStyle = sProgramTitleStyle; episodeStyle = sEpisodeTitleStyle; } - if (TextUtils.isEmpty(title)) { - title = getResources().getString(R.string.program_title_for_no_information); - } - if (mTableEntry.scheduledRecording != null) { - //TODO(dvr): use a proper icon for UI status. - title = "®" + title; - } SpannableStringBuilder description = new SpannableStringBuilder(); description.append(title); @@ -306,11 +340,12 @@ public class ProgramItemView extends TextView { measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); int start = GuideUtils.convertMillisToPixel(entry.entryStartUtcMillis); - int guideStart = GuideUtils.convertMillisToPixel(fromUtcMillis); + int guideStart = GuideUtils.convertMillisToPixel(programManager.getFromUtcMillis()); layoutVisibleArea(guideStart - start); // Maximum width for us to use a ripple - mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); + mMaxWidthForRipple = GuideUtils.convertMillisToPixel( + programManager.getFromUtcMillis(), programManager.getToUtcMillis()); } /** @@ -339,13 +374,14 @@ public class ProgramItemView extends TextView { } } - public void clearValues() { + public void onUnbind() { if (getHandler() != null) { getHandler().removeCallbacks(mUpdateFocus); } setTag(null); - mTableEntry = null; + setOnFocusChangeListener(null); + setOnClickListener(null); } private static int getProgress(long start, long end) { diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index 03aea5ad..88ba435e 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -16,6 +16,7 @@ package com.android.tv.guide; +import android.content.Context; import android.content.res.Resources; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -32,24 +33,30 @@ import com.android.tv.guide.ProgramManager.TableEntry; * Adapts a program list for a specific channel from {@link ProgramManager} to a row of the program * guide table. */ -public class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter.ProgramViewHolder> - implements TableEntriesUpdatedListener { +public class ProgramListAdapter extends + RecyclerView.Adapter<ProgramListAdapter.ProgramViewHolder> implements + TableEntriesUpdatedListener { private static final String TAG = "ProgramListAdapter"; private static final boolean DEBUG = false; - private final ProgramManager mProgramManager; - private final int mChannelIndex; private final String mNoInfoProgramTitle; private final String mBlockedProgramTitle; + private final ProgramManager mProgramManager; + private final int mChannelIndex; + private long mChannelId; - public ProgramListAdapter(Resources res, ProgramManager programManager, int channelIndex) { - setHasStableIds(true); + public ProgramListAdapter(Context context, ProgramManager programManager, + int channelIndex) { + Resources res = context.getResources(); + mNoInfoProgramTitle = res.getString( + R.string.program_title_for_no_information); + mBlockedProgramTitle = res.getString( + R.string.program_title_for_blocked_channel); + mProgramManager = programManager; mChannelIndex = channelIndex; - mNoInfoProgramTitle = res.getString(R.string.program_title_for_no_information); - mBlockedProgramTitle = res.getString(R.string.program_title_for_blocked_channel); onTableEntriesUpdated(); } @@ -69,6 +76,14 @@ public class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter. return mProgramManager; } + public String getNoInfoProgramTitle() { + return mNoInfoProgramTitle; + } + + public String getBlockedProgramTitle() { + return mBlockedProgramTitle; + } + @Override public int getItemCount() { return mProgramManager.getTableEntryCount(mChannelId); @@ -80,15 +95,8 @@ public class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter. } @Override - public long getItemId(int position) { - return mProgramManager.getTableEntry(mChannelId, position).getId(); - } - - @Override public void onBindViewHolder(ProgramViewHolder holder, int position) { - TableEntry tableEntry = mProgramManager.getTableEntry(mChannelId, position); - String gapTitle = tableEntry.isBlocked() ? mBlockedProgramTitle : mNoInfoProgramTitle; - holder.onBind(tableEntry, this.getProgramManager(), gapTitle); + holder.onBind(mProgramManager.getTableEntry(mChannelId, position), this); } @Override @@ -108,16 +116,16 @@ public class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter. super(itemView); } - public void onBind(TableEntry entry, ProgramManager programManager, String gapTitle) { + public void onBind(TableEntry entry, ProgramListAdapter adapter) { if (DEBUG) { Log.d(TAG, "onBind. View = " + itemView + ", Entry = " + entry); } - ((ProgramItemView) itemView).setValues(entry, programManager.getSelectedGenreId(), - programManager.getFromUtcMillis(), programManager.getToUtcMillis(), gapTitle); + + ((ProgramItemView) itemView).onBind(entry, adapter); } public void onUnbind() { - ((ProgramItemView) itemView).clearValues(); + ((ProgramItemView) itemView).onUnbind(); } } } diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java index fe1a981f..df52abbe 100644 --- a/src/com/android/tv/guide/ProgramManager.java +++ b/src/com/android/tv/guide/ProgramManager.java @@ -17,17 +17,14 @@ package com.android.tv.guide; import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.util.ArraySet; import android.util.Log; +import com.android.tv.common.CollectionUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.ScheduledRecording; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -58,7 +55,6 @@ public class ProgramManager { private final TvInputManagerHelper mTvInputManagerHelper; private final ChannelDataManager mChannelDataManager; private final ProgramDataManager mProgramDataManager; - private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled private long mStartUtcMillis; private long mEndUtcMillis; @@ -78,8 +74,6 @@ public class ProgramManager { /** Program corresponding to the entry. {@code null} means that this entry is a gap. */ public final Program program; - public final ScheduledRecording scheduledRecording; - /** Start time of entry in UTC milliseconds. */ public final long entryStartUtcMillis; @@ -88,39 +82,34 @@ public class ProgramManager { private final boolean mIsBlocked; + private TableEntry(long startUtcMillis, long endUtcMillis) { + this(INVALID_ID, null, startUtcMillis, endUtcMillis, false); + } + private TableEntry(long channelId, long startUtcMillis, long endUtcMillis) { this(channelId, null, startUtcMillis, endUtcMillis, false); } private TableEntry(long channelId, long startUtcMillis, long endUtcMillis, boolean blocked) { - this(channelId, null, null, startUtcMillis, endUtcMillis, blocked); + this(channelId, null, startUtcMillis, endUtcMillis, blocked); } - private TableEntry(long channelId, Program program, long entryStartUtcMillis, - long entryEndUtcMillis, boolean isBlocked) { - this(channelId, program, null, entryStartUtcMillis, entryEndUtcMillis, isBlocked); + private TableEntry(long channelId, Program program, + long entryStartUtcMillis, long entryEndUtcMillis) { + this(channelId, program, entryStartUtcMillis, entryEndUtcMillis, false); } - private TableEntry(long channelId, Program program, ScheduledRecording scheduledRecording, + private TableEntry(long channelId, Program program, long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) { this.channelId = channelId; this.program = program; - this.scheduledRecording = scheduledRecording; this.entryStartUtcMillis = entryStartUtcMillis; this.entryEndUtcMillis = entryEndUtcMillis; mIsBlocked = isBlocked; } /** - * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. - */ - public long getId() { - // using a negative entryEndUtcMillis keeps it from conflicting with program Id - return program != null ? program.getId() : -entryEndUtcMillis; - } - - /** * Returns true if this is a gap. */ public boolean isGap() { @@ -178,10 +167,9 @@ public class ProgramManager { // Should be matched with mSelectedGenreId always. private List<Channel> mFilteredChannels = mChannels; - private final Set<Listener> mListeners = new ArraySet<>(); - private final Set<TableEntriesUpdatedListener> mTableEntriesUpdatedListeners = new ArraySet<>(); - - private final Set<TableEntryChangedListener> mTableEntryChangedListeners = new ArraySet<>(); + private final Set<Listener> mListeners = CollectionUtils.createSmallSet(); + private final Set<TableEntriesUpdatedListener> mTableEntriesUpdatedListeners = CollectionUtils + .createSmallSet(); private final ChannelDataManager.Listener mChannelDataManagerListener = new ChannelDataManager.Listener() { @@ -209,49 +197,12 @@ public class ProgramManager { } }; - private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener = - new DvrDataManager.ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording scheduledRecording) { - TableEntry oldEntry = getTableEntry(scheduledRecording); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, - scheduledRecording, oldEntry.entryStartUtcMillis, - oldEntry.entryEndUtcMillis, oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); - } - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording scheduledRecording) { - TableEntry oldEntry = getTableEntry(scheduledRecording); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, null, - oldEntry.entryStartUtcMillis, oldEntry.entryEndUtcMillis, - oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); - } - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording scheduledRecording) { - TableEntry oldEntry = getTableEntry(scheduledRecording); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, - scheduledRecording, oldEntry.entryStartUtcMillis, - oldEntry.entryEndUtcMillis, oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); - } - } - }; - public ProgramManager(TvInputManagerHelper tvInputManagerHelper, - ChannelDataManager channelDataManager, ProgramDataManager programDataManager, - @Nullable DvrDataManager dvrDataManager) { + ChannelDataManager channelDataManager, + ProgramDataManager programDataManager) { mTvInputManagerHelper = tvInputManagerHelper; mChannelDataManager = channelDataManager; mProgramDataManager = programDataManager; - mDvrDataManager = dvrDataManager; } public void programGuideVisibilityChanged(boolean visible) { @@ -259,61 +210,41 @@ public class ProgramManager { if (visible) { mChannelDataManager.addListener(mChannelDataManagerListener); mProgramDataManager.addListener(mProgramDataManagerListener); - if (mDvrDataManager != null) { - mDvrDataManager.addScheduledRecordingListener(mScheduledRecordingListener); - } } else { mChannelDataManager.removeListener(mChannelDataManagerListener); mProgramDataManager.removeListener(mProgramDataManagerListener); - if (mDvrDataManager != null) { - mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); - } } } /** - * Adds a {@link Listener}. + * Add a {@link Listener}. */ public void addListener(Listener listener) { mListeners.add(listener); } /** - * Registers a listener to be invoked when table entries are updated. + * Register a listener to be invoked when table entries are updated. */ public void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.add(listener); } /** - * Registers a listener to be invoked when a table entry is changed. - */ - public void addTableEntryChangedListener(TableEntryChangedListener listener) { - mTableEntryChangedListeners.add(listener); - } - - /** - * Removes a {@link Listener}. + * Remove a {@link Listener}. */ public void removeListener(Listener listener) { mListeners.remove(listener); } /** - * Removes a previously installed table entries update listener. + * Remove a previously installed table entries update listener. */ public void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.remove(listener); } /** - * Removes a previously installed table entry changed listener. - */ - public void removeTableEntryChangedListener(TableEntryChangedListener listener) { - mTableEntryChangedListeners.remove(listener); - } - - /** * Build genre filters based on the current programs. * This categories channels by its current program's canonical genres * and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list @@ -435,7 +366,6 @@ public class ProgramManager { } else if (lastEntry.entryEndUtcMillis == Long.MAX_VALUE) { entries.remove(entries.size() - 1); entries.add(new TableEntry(lastEntry.channelId, lastEntry.program, - lastEntry.scheduledRecording, lastEntry.entryStartUtcMillis, mEndUtcMillis, lastEntry.mIsBlocked)); } @@ -473,37 +403,6 @@ public class ProgramManager { } } - private void notifyTableEntryUpdated(TableEntry entry) { - for (TableEntryChangedListener listener : mTableEntryChangedListeners) { - listener.onTableEntryChanged(entry); - } - } - - private void updateEntry(TableEntry old, TableEntry newEntry) { - List<TableEntry> entries = mChannelIdEntriesMap.get(old.channelId); - int index = entries.indexOf(old); - entries.set(index, newEntry); - notifyTableEntryUpdated(newEntry); - } - - @Nullable - private TableEntry getTableEntry(ScheduledRecording scheduledRecording) { - return getTableEntry(scheduledRecording.getChannelId(), scheduledRecording.getProgramId()); - } - - @Nullable - private TableEntry getTableEntry(long channelId, long entryId) { - List<TableEntry> entries = mChannelIdEntriesMap.get(channelId); - if (entries != null) { - for (TableEntry entry : entries) { - if (entry.getId() == entryId) { - return entry; - } - } - } - return null; - } - /** * Returns the start time of currently managed time range, in UTC millisecond. */ @@ -572,14 +471,6 @@ public class ProgramManager { } /** - * Returns the index of channel with {@code channelId} within the currently managed channels. - * Returns -1 if such a channel is not found. - */ - public int getChannelIndex(long channelId) { - return getChannelIndex(mChannelDataManager.getChannel(channelId)); - } - - /** * Returns the number of "entries", which lies within the currently managed time range, for a * given {@code channelId}. */ @@ -620,10 +511,8 @@ public class ProgramManager { lastProgramEndTime = programStartTime; } if (programEndTime > lastProgramEndTime) { - ScheduledRecording scheduledRecording = mDvrDataManager == null ? null - : mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); - entries.add(new TableEntry(channelId, program, scheduledRecording, - lastProgramEndTime, programEndTime, false)); + entries.add(new TableEntry(channelId, program, lastProgramEndTime, + programEndTime)); lastProgramEndTime = programEndTime; } } @@ -636,8 +525,7 @@ public class ProgramManager { // the first entry from UI perspective. So we clip it out. entries.remove(0); entries.set(0, new TableEntry(secondEntry.channelId, secondEntry.program, - secondEntry.scheduledRecording, mStartUtcMillis, - secondEntry.entryEndUtcMillis, secondEntry.mIsBlocked)); + mStartUtcMillis, secondEntry.entryEndUtcMillis)); } } return entries; @@ -667,10 +555,6 @@ public class ProgramManager { void onTableEntriesUpdated(); } - public interface TableEntryChangedListener { - void onTableEntryChanged(TableEntry entry); - } - public static class ListenerAdapter implements Listener { @Override public void onGenresUpdated() { } @@ -714,24 +598,9 @@ public class ProgramManager { } /** - * Returns the program index of the program with {@code entryId} or -1 if not found. - */ - public int getProgramIdIndex(long channelId, long entryId) { - List<TableEntry> entries = mChannelIdEntriesMap.get(channelId); - if (entries != null) { - for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getId() == entryId) { - return i; - } - } - } - return -1; - } - - /** - * Returns the program index of the program at {@code time} or -1 if not found. + * Returns the program index of the program at {@code time}. */ - public int getProgramIndexAtTime(long channelId, long time) { + public int getProgramIndex(long channelId, long time) { List<TableEntry> entries = mChannelIdEntriesMap.get(channelId); for (int i = 0; i < entries.size(); ++i) { TableEntry entry = entries.get(i); diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index 54b864db..4f38b879 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -306,7 +306,7 @@ public class ProgramRow extends TimelineGridView { public void resetScroll(int scrollOffset) { long startTime = GuideUtils.convertPixelToMillis(scrollOffset) + mProgramManager.getStartTime(); - int position = mChannel == null ? -1 : mProgramManager.getProgramIndexAtTime( + int position = mChannel == null ? -1 : mProgramManager.getProgramIndex( mChannel.getId(), startTime); if (position < 0) { getLayoutManager().scrollToPosition(0); diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 83755b5f..a86c1332 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -26,9 +26,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.media.tv.TvContentRating; -import android.media.tv.TvInputInfo; import android.os.Handler; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.RecycledViewPool; @@ -45,15 +43,11 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.TvApplication; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; -import com.android.tv.util.ImageCache; -import com.android.tv.util.ImageLoader; -import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -63,8 +57,8 @@ import java.util.List; /** * Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */ -public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder> - implements ProgramManager.TableEntryChangedListener { +public class ProgramTableAdapter extends + RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder> { private static final String TAG = "ProgramTableAdapter"; private static final boolean DEBUG = false; @@ -90,10 +84,10 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte private final int mDetailPadding; private final TextAppearanceSpan mEpisodeTitleStyle; - public ProgramTableAdapter(Context context, ProgramManager programManager, - ProgramGuide programGuide) { + public ProgramTableAdapter(Context context, TvInputManagerHelper tvInputManagerHelper, + ProgramManager programManager, ProgramGuide programGuide) { mContext = context; - mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper(); + mTvInputManagerHelper = tvInputManagerHelper; mProgramManager = programManager; mProgramGuide = programGuide; @@ -146,7 +140,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } }); update(); - mProgramManager.addTableEntryChangedListener(this); } private void update() { @@ -156,8 +149,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } mProgramListAdapters.clear(); for (int i = 0; i < mProgramManager.getChannelCount(); i++) { - ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(), - mProgramManager, i); + ProgramListAdapter listAdapter = new ProgramListAdapter(mContext, mProgramManager, i); mProgramManager.addTableEntriesUpdatedListener(listAdapter); mProgramListAdapters.add(listAdapter); } @@ -187,14 +179,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte return new ProgramRowHolder(itemView); } - @Override - public void onTableEntryChanged(ProgramManager.TableEntry tableEntry) { - int channelIndex = mProgramManager.getChannelIndex(tableEntry.channelId); - int pos = mProgramManager.getProgramIdIndex(tableEntry.channelId, tableEntry.getId()); - if (DEBUG) Log.d(TAG, "update(" + channelIndex + ", " + pos + ")"); - mProgramListAdapters.get(channelIndex).notifyItemChanged(pos, tableEntry); - } - // TODO: make it static public class ProgramRowHolder extends RecyclerView.ViewHolder implements ProgramRow.ChildFocusListener { @@ -239,9 +223,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte private final TextView mChannelNameView; private final ImageView mChannelLogoView; private final ImageView mChannelBlockView; - private final ImageView mInputLogoView; - - private boolean mIsInputLogoVisible; public ProgramRowHolder(View itemView) { super(itemView); @@ -263,7 +244,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte mChannelNameView = (TextView) mContainer.findViewById(R.id.channel_name); mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo); mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block); - mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo); } public void onBind(int position) { @@ -287,8 +267,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte if (DEBUG) Log.d(TAG, "onBindChannel " + channel); mChannel = channel; - mInputLogoView.setVisibility(View.GONE); - mIsInputLogoVisible = false; if (channel == null) { mChannelNumberView.setVisibility(View.GONE); mChannelNameView.setVisibility(View.GONE); @@ -489,43 +467,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } } - /** - * Update tv input logo. It should be called when the visible child item in ProgramGrid - * changed. - */ - public void updateInputLogo(int lastPosition, boolean forceShow) { - if (mChannel == null) { - mInputLogoView.setVisibility(View.GONE); - mIsInputLogoVisible = false; - return; - } - - boolean showLogo = forceShow; - if (!showLogo) { - Channel lastChannel = mProgramManager.getChannel(lastPosition); - if (lastChannel == null - || !mChannel.getInputId().equals(lastChannel.getInputId())) { - showLogo = true; - } - } - - if (showLogo) { - if (!mIsInputLogoVisible) { - mIsInputLogoVisible = true; - TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId()); - if (info != null) { - LoadTvInputLogoTask task = new LoadTvInputLogoTask( - itemView.getContext(), ImageCache.getInstance(), info); - ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task); - } - } - } else { - mInputLogoView.setVisibility(View.GONE); - mInputLogoView.setImageDrawable(null); - mIsInputLogoVisible = false; - } - } - private void updateTextView(TextView textView, String text) { if (!TextUtils.isEmpty(text)) { textView.setVisibility(View.VISIBLE); @@ -546,14 +487,6 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte mChannelLogoView.setVisibility(View.VISIBLE); } - private void updateInputLogoInternal(@NonNull Bitmap tvInputLogo) { - if (!mIsInputLogoVisible) { - return; - } - mInputLogoView.setImageBitmap(tvInputLogo); - mInputLogoView.setVisibility(View.VISIBLE); - } - private void onHorizontalScrolled() { if (mDetailInAnimator != null) { mHandler.removeCallbacks(mDetailInStarter); @@ -593,17 +526,4 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } }; } - - private static ImageLoaderCallback<ProgramRowHolder> createTvInputLogoLoadedCallback( - final TvInputInfo info, ProgramRowHolder holder) { - return new ImageLoaderCallback<ProgramRowHolder>(holder) { - @Override - public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) { - if (logo != null && info.getId() - .equals(holder.mChannel.getInputId())) { - holder.updateInputLogoInternal(logo); - } - } - }; - } } diff --git a/src/com/android/tv/guide/TimelineRow.java b/src/com/android/tv/guide/TimelineRow.java index 3f0c8678..891b14cd 100644 --- a/src/com/android/tv/guide/TimelineRow.java +++ b/src/com/android/tv/guide/TimelineRow.java @@ -64,9 +64,7 @@ public class TimelineRow extends TimelineGridView { public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); // Reset scroll - if (isAttachedToWindow()) { - scrollTo(getScrollOffset(), false); - } + scrollTo(getScrollOffset(), false); } @Override diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java index 2d72b06f..1848a3ce 100644 --- a/src/com/android/tv/menu/ActionCardView.java +++ b/src/com/android/tv/menu/ActionCardView.java @@ -93,7 +93,4 @@ public class ActionCardView extends FrameLayout implements ItemListRowView.CardV Log.d(TAG, "onDeselected: action=" + mLabelView.getText()); } } - - @Override - public void onRecycled() { } } diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java index b4500dd1..25d4e313 100644 --- a/src/com/android/tv/menu/BaseCardView.java +++ b/src/com/android/tv/menu/BaseCardView.java @@ -85,9 +85,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo } @Override - public void onRecycled() { } - - @Override public void onSelected() { if (isAttachedToWindow() && getVisibility() == View.VISIBLE) { startFocusAnimation(SCALE_FACTOR_1F); diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java index f932d75d..1e416e5b 100644 --- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java +++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java @@ -25,11 +25,11 @@ import android.support.annotation.NonNull; import android.util.Log; import com.android.tv.R; -import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.util.SoftPreconditions; import java.util.List; diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java index 200f4ac0..51867d0b 100644 --- a/src/com/android/tv/menu/ChannelsRowAdapter.java +++ b/src/com/android/tv/menu/ChannelsRowAdapter.java @@ -18,9 +18,7 @@ package com.android.tv.menu; import android.content.Context; import android.content.Intent; -import android.media.tv.TvInputInfo; import android.os.Build; -import android.support.v4.os.BuildCompat; import android.view.View; import com.android.tv.MainActivity; @@ -31,8 +29,6 @@ import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; import com.android.tv.recommendation.Recommender; import com.android.tv.util.SetupUtils; -import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -41,16 +37,16 @@ import java.util.List; * An adapter of the Channels row. */ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> { - // There are four special cards: guide, setup, dvr, record, applink. - private static final int SIZE_OF_VIEW_TYPE = 5; + // There are four special cards: guide, setup, dvr, applink. + private static final int SIZE_OF_VIEW_TYPE = 4; private final Context mContext; private final Tracker mTracker; private final Recommender mRecommender; private final int mMaxCount; private final int mMinCount; - private final boolean mDvrFeatureEnabled; - private final int[] mViewType = new int[SIZE_OF_VIEW_TYPE]; + private boolean mShowDvrCard; + private int[] mViewType = new int[SIZE_OF_VIEW_TYPE]; private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() { @Override @@ -71,28 +67,11 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> private final View.OnClickListener mDvrOnClickListener = new View.OnClickListener() { @Override public void onClick(View view) { - Utils.showToastMessageForDeveloperFeature(view.getContext()); mTracker.sendMenuClicked(R.string.channels_item_dvr); getMainActivity().getOverlayManager().showDvrManager(); } }; - private final View.OnClickListener mRecordOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - Utils.showToastMessageForDeveloperFeature(view.getContext()); - RecordCardView v = ((RecordCardView) view); - boolean isRecording = v.isRecording(); - mTracker.sendMenuClicked(isRecording ? R.string.channels_item_record_start - : R.string.channels_item_record_stop); - if (!isRecording) { - v.startRecording(); - } else { - v.stopRecording(); - } - } - }; - private final View.OnClickListener mAppLinkOnClickListener = new View.OnClickListener() { @Override public void onClick(View view) { @@ -123,7 +102,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> mRecommender = recommender; mMinCount = minCount; mMaxCount = maxCount; - mDvrFeatureEnabled = CommonFeatures.DVR.isEnabled(mContext) && BuildCompat.isAtLeastN(); + mShowDvrCard = CommonFeatures.DVR.isEnabled(mContext); } @Override @@ -152,8 +131,6 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> viewHolder.itemView.setOnClickListener(mAppLinkOnClickListener); } else if (viewType == R.layout.menu_card_dvr) { viewHolder.itemView.setOnClickListener(mDvrOnClickListener); - } else if (viewType == R.layout.menu_card_record) { - viewHolder.itemView.setOnClickListener(mRecordOnClickListener); } else { viewHolder.itemView.setTag(getItemList().get(position)); viewHolder.itemView.setOnClickListener(mChannelOnClickListener); @@ -163,33 +140,17 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> @Override public void update() { List<Channel> channelList = new ArrayList<>(); - Channel dummyChannel = new Channel.Builder().build(); + Channel dummyChannel = new Channel.Builder() + .build(); // For guide item channelList.add(dummyChannel); // For setup item - TvInputManagerHelper inputManager = TvApplication.getSingletons(mContext) - .getTvInputManagerHelper(); - boolean showSetupCard = SetupUtils.getInstance(mContext).hasNewInput(inputManager); + boolean showSetupCard = SetupUtils.getInstance(mContext) + .hasNewInput(((MainActivity) mContext).getTvInputManagerHelper()); Channel currentChannel = ((MainActivity) mContext).getCurrentChannel(); boolean showAppLinkCard = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && currentChannel != null && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE; - boolean showDvrCard = false; - boolean showRecordCard = false; - if (mDvrFeatureEnabled) { - for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) { - if (info.canRecord()) { - showDvrCard = true; - break; - } - } - if (currentChannel != null && currentChannel.getInputId() != null) { - TvInputInfo inputInfo = inputManager.getTvInputInfo(currentChannel.getInputId()); - if ((inputInfo.canRecord() && inputInfo.getTunerCount() > 1)) { - showRecordCard = true; - } - } - } mViewType[0] = R.layout.menu_card_guide; int index = 1; @@ -197,14 +158,10 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> channelList.add(dummyChannel); mViewType[index++] = R.layout.menu_card_setup; } - if (showDvrCard) { + if (mShowDvrCard) { channelList.add(dummyChannel); mViewType[index++] = R.layout.menu_card_dvr; } - if (showRecordCard) { - channelList.add(currentChannel); - mViewType[index++] = R.layout.menu_card_record; - } if (showAppLinkCard) { channelList.add(currentChannel); mViewType[index++] = R.layout.menu_card_app_link; diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java index 4919c595..e9362a78 100644 --- a/src/com/android/tv/menu/ItemListRowView.java +++ b/src/com/android/tv/menu/ItemListRowView.java @@ -41,7 +41,6 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe public interface CardView<T> { void onBind(T row, boolean selected); - void onRecycled(); void onSelected(); void onDeselected(); } @@ -207,13 +206,6 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe cardView.onBind(mItemList.get(position), cardView.equals(mItemListView.mSelectedCard)); } - @Override - public void onViewRecycled(MyViewHolder viewHolder) { - super.onViewRecycled(viewHolder); - CardView<T> cardView = (CardView<T>) viewHolder.itemView; - cardView.onRecycled(); - } - public static class MyViewHolder extends RecyclerView.ViewHolder { public MyViewHolder(View view) { super(view); diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java index 7bb0787e..613e0d62 100644 --- a/src/com/android/tv/menu/Menu.java +++ b/src/com/android/tv/menu/Menu.java @@ -56,7 +56,7 @@ public class Menu { @IntDef({REASON_NONE, REASON_GUIDE, REASON_PLAY_CONTROLS_PLAY, REASON_PLAY_CONTROLS_PAUSE, REASON_PLAY_CONTROLS_PLAY_PAUSE, REASON_PLAY_CONTROLS_REWIND, REASON_PLAY_CONTROLS_FAST_FORWARD, REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS, - REASON_PLAY_CONTROLS_JUMP_TO_NEXT, REASON_RECORDING_PLAYBACK}) + REASON_PLAY_CONTROLS_JUMP_TO_NEXT}) public @interface MenuShowReason {} public static final int REASON_NONE = 0; public static final int REASON_GUIDE = 1; @@ -67,7 +67,6 @@ public class Menu { public static final int REASON_PLAY_CONTROLS_FAST_FORWARD = 6; public static final int REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS = 7; public static final int REASON_PLAY_CONTROLS_JUMP_TO_NEXT = 8; - public static final int REASON_RECORDING_PLAYBACK = 9; private static final List<String> sRowIdListForReason = new ArrayList<>(); static { @@ -80,7 +79,6 @@ public class Menu { sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_FAST_FORWARD sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_NEXT - sRowIdListForReason.add(PlayControlsRow.ID); // REASON_RECORDING_PLAYBACK } private static final String SCREEN_NAME = "Menu"; diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java index 86153084..b45e88c2 100644 --- a/src/com/android/tv/menu/MenuAction.java +++ b/src/com/android/tv/menu/MenuAction.java @@ -36,11 +36,8 @@ public class MenuAction { public static final MenuAction SELECT_DISPLAY_MODE_ACTION = new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE, R.drawable.ic_tvoption_aspect); - public static final MenuAction PIP_IN_APP_ACTION = - new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_IN_APP_PIP, - R.drawable.ic_tvoption_pip); - public static final MenuAction SYSTEMWIDE_PIP_ACTION = - new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP, + public static final MenuAction PIP_ACTION = + new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_PIP, R.drawable.ic_tvoption_pip); public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION = new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO, diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java index 1f377f54..265ad840 100644 --- a/src/com/android/tv/menu/MenuLayoutManager.java +++ b/src/com/android/tv/menu/MenuLayoutManager.java @@ -35,7 +35,7 @@ import android.view.ViewGroup.MarginLayoutParams; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.util.SoftPreconditions; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -318,11 +318,6 @@ public class MenuLayoutManager { if (!indexValid) { return; } - MenuRow row = mMenuRows.get(position); - if (!row.isVisible()) { - Log.e(TAG, "Selecting invisible row: " + position); - return; - } if (Utils.isIndexValid(mMenuRowViews, mSelectedPosition)) { mMenuRowViews.get(mSelectedPosition).onDeselected(); } @@ -365,11 +360,6 @@ public class MenuLayoutManager { if (!newIndexValid) { return; } - MenuRow row = mMenuRows.get(position); - if (!row.isVisible()) { - Log.e(TAG, "Moving to the invisible row: " + position); - return; - } if (mAnimatorSet != null) { // Do not cancel the animation here. The property values should be set to the end values // when the animation finishes. @@ -797,9 +787,9 @@ public class MenuLayoutManager { } private static final class ViewPropertyValueHolder { - public final Property<View, Float> property; - public final View view; - public final float value; + public Property<View, Float> property; + public View view; + public float value; public ViewPropertyValueHolder(Property<View, Float> property, View view, float value) { this.property = property; diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java index e012dfca..df91ddf3 100644 --- a/src/com/android/tv/menu/MenuView.java +++ b/src/com/android/tv/menu/MenuView.java @@ -117,11 +117,10 @@ public class MenuView extends FrameLayout implements IMenuView { } initializeChildren(); update(true); - int position = getItemPosition(rowIdToSelect); - if (position == -1 || !mMenuRows.get(position).isVisible()) { - // Channels row is always visible. - position = getItemPosition(ChannelsRow.ID); + if (rowIdToSelect == null) { + rowIdToSelect = ChannelsRow.ID; } + int position = getItemPosition(rowIdToSelect); setSelectedPosition(position); // Change the visibility as late as possible to avoid the unnecessary animation. setVisibility(VISIBLE); diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index 058d5108..f0853c40 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -19,7 +19,6 @@ package com.android.tv.menu; import android.content.Context; import android.content.res.Resources; import android.text.format.DateFormat; -import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -28,7 +27,6 @@ import android.widget.TextView; import com.android.tv.R; import com.android.tv.TimeShiftManager; import com.android.tv.TimeShiftManager.TimeShiftActionId; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.menu.Menu.MenuShowReason; @@ -251,16 +249,9 @@ public class PlayControlsRowView extends MenuRowView { } private void initializeTimeline() { - if (mTimeShiftManager.isRecordingPlayback()) { - mProgramStartTimeMs = mTimeShiftManager.getRecordStartTimeMs(); - mProgramEndTimeMs = mTimeShiftManager.getRecordEndTimeMs(); - } else { - Program program = mTimeShiftManager.getProgramAt( - mTimeShiftManager.getCurrentPositionMs()); - mProgramStartTimeMs = program.getStartTimeUtcMillis(); - mProgramEndTimeMs = program.getEndTimeUtcMillis(); - } - SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs); + Program program = mTimeShiftManager.getProgramAt(mTimeShiftManager.getCurrentPositionMs()); + mProgramStartTimeMs = program.getStartTimeUtcMillis(); + mProgramEndTimeMs = program.getEndTimeUtcMillis(); } private void updateMenuVisibility() { @@ -366,6 +357,14 @@ public class PlayControlsRowView extends MenuRowView { mTimeIndicator.setVisibility(View.INVISIBLE); return; } + if (mTimeShiftManager.isPlayForRecording()) { + mProgramStartTimeMs = mTimeShiftManager.getRecordStartTimeMs(); + mProgramEndTimeMs = Math.max(mProgramStartTimeMs, + mTimeShiftManager.getRecordEndTimeMs()); + if (mProgramStartTimeMs > mProgramEndTimeMs) { + mProgramEndTimeMs = mProgramStartTimeMs; + } + } long currentPositionMs = mTimeShiftManager.getCurrentPositionMs(); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mTimeText.getLayoutParams(); @@ -423,18 +422,15 @@ public class PlayControlsRowView extends MenuRowView { private void updateRecTimeText() { if (isEnabled()) { - if (mTimeShiftManager.isRecordingPlayback()) { - mProgramStartTimeText.setVisibility(View.GONE); - } else { - mProgramStartTimeText.setVisibility(View.VISIBLE); - mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs)); - } + mProgramStartTimeText.setVisibility(View.VISIBLE); mProgramEndTimeText.setVisibility(View.VISIBLE); - mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs)); } else { - mProgramStartTimeText.setVisibility(View.GONE); - mProgramEndTimeText.setVisibility(View.GONE); + mProgramStartTimeText.setVisibility(View.INVISIBLE); + mProgramEndTimeText.setVisibility(View.INVISIBLE); + return; } + mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs)); + mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs)); } private void updateButtons() { @@ -482,9 +478,7 @@ public class PlayControlsRowView extends MenuRowView { } private String getTimeString(long timeMs) { - return mTimeShiftManager.isRecordingPlayback() - ? DateUtils.formatElapsedTime(timeMs / 1000) - : mTimeFormat.format(timeMs); + return mTimeFormat.format(timeMs); } private int convertDurationToPixel(long duration) { diff --git a/src/com/android/tv/menu/RecordCardView.java b/src/com/android/tv/menu/RecordCardView.java deleted file mode 100644 index de30894e..00000000 --- a/src/com/android/tv/menu/RecordCardView.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2015 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.android.tv.menu; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.Program; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * A view to render an item of TV options. - */ -public class RecordCardView extends SimpleCardView implements - DvrDataManager.ScheduledRecordingListener { - private static final String TAG = MenuView.TAG; - private static final boolean DEBUG = MenuView.DEBUG; - private static final long MIN_PROGRAM_RECORD_DURATION = TimeUnit.MINUTES.toMillis(5); - - private ImageView mIconView; - private TextView mLabelView; - private Channel mCurrentChannel; - private final DvrManager mDvrManager; - private final DvrDataManager mDvrDataManager; - private boolean mIsRecording; - private ScheduledRecording mCurrentRecording; - - public RecordCardView(Context context) { - this(context, null); - } - - public RecordCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecordCardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - } - - @Override - public void onBind(Channel channel, boolean selected) { - super.onBind(channel, selected); - mIconView = (ImageView) findViewById(R.id.record_icon); - mLabelView = (TextView) findViewById(R.id.record_label); - mCurrentChannel = channel; - mCurrentRecording = null; - for (ScheduledRecording recording : mDvrDataManager.getStartedRecordings()) { - if (recording.getChannelId() == channel.getId()) { - mIsRecording = true; - mCurrentRecording = recording; - } - } - mDvrDataManager.addScheduledRecordingListener(this); - updateCardView(); - } - - @Override - public void onRecycled() { - super.onRecycled(); - mDvrDataManager.removeScheduledRecordingListener(this); - } - - public boolean isRecording() { - return mIsRecording; - } - - public void startRecording() { - showStartRecordingDialog(); - } - - public void stopRecording() { - mDvrManager.stopRecording(mCurrentRecording); - } - - private void updateCardView() { - if (mIsRecording) { - mIconView.setImageResource(R.drawable.ic_record_stop); - mLabelView.setText(R.string.channels_item_record_stop); - } else { - mIconView.setImageResource(R.drawable.ic_record_start); - mLabelView.setText(R.string.channels_item_record_start); - } - } - - private void showStartRecordingDialog() { - final long endOfProgram = -1; - - final List<CharSequence> items = new ArrayList<>(); - final List<Long> durations = new ArrayList<>(); - Resources res = getResources(); - items.add(res.getString(R.string.recording_start_dialog_10_min_duration)); - durations.add(TimeUnit.MINUTES.toMillis(10)); - items.add(res.getString(R.string.recording_start_dialog_30_min_duration)); - durations.add(TimeUnit.MINUTES.toMillis(30)); - items.add(res.getString(R.string.recording_start_dialog_1_hour_duration)); - durations.add(TimeUnit.HOURS.toMillis(1)); - items.add(res.getString(R.string.recording_start_dialog_3_hours_duration)); - durations.add(TimeUnit.HOURS.toMillis(3)); - - Program currenProgram = ((MainActivity) getContext()).getCurrentProgram(false); - if (currenProgram != null) { - long duration = currenProgram.getEndTimeUtcMillis() - System.currentTimeMillis(); - if (duration > MIN_PROGRAM_RECORD_DURATION) { - items.add(res.getString(R.string.recording_start_dialog_till_end_of_program)); - durations.add(duration); - } - } - - DialogInterface.OnClickListener onClickListener - = new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, int which) { - long startTime = System.currentTimeMillis(); - long endTime = System.currentTimeMillis() + durations.get(which); - mDvrManager.addSchedule(mCurrentChannel, startTime, endTime); - dialog.dismiss(); - } - }; - new AlertDialog.Builder(getContext()) - .setItems(items.toArray(new CharSequence[items.size()]), onClickListener) - .create() - .show(); - } - - @Override - public void onScheduledRecordingAdded(ScheduledRecording recording) { - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording recording) { - if (recording.getChannelId() != mCurrentChannel.getId()) { - return; - } - if (mIsRecording) { - mIsRecording = false; - mCurrentRecording = null; - updateCardView(); - } - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording recording) { - if (recording.getChannelId() != mCurrentChannel.getId()) { - return; - } - int state = recording.getState(); - if (state == ScheduledRecording.STATE_RECORDING_FAILED - || state == ScheduledRecording.STATE_RECORDING_FINISHED) { - mIsRecording = false; - mCurrentRecording = null; - updateCardView(); - } else if (state == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { - mIsRecording = true; - mCurrentRecording = recording; - updateCardView(); - } - } -} diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java index ba84247b..82525456 100644 --- a/src/com/android/tv/menu/TvOptionsRowAdapter.java +++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java @@ -19,7 +19,6 @@ package com.android.tv.menu; import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.annotation.VisibleForTesting; -import android.support.v4.os.BuildCompat; import com.android.tv.Features; import com.android.tv.R; @@ -40,13 +39,10 @@ import java.util.List; */ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { private int mPositionPipAction; - // If mInAppPipAction is false, system-wide PIP is used. - private boolean mInAppPipAction = true; - private final Context mContext; + private boolean mHasPipAction = true; public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) { super(context, customActions); - mContext = context; } @Override @@ -56,8 +52,8 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { setOptionChangedListener(MenuAction.SELECT_CLOSED_CAPTION_ACTION); actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION); setOptionChangedListener(MenuAction.SELECT_DISPLAY_MODE_ACTION); - actionList.add(MenuAction.PIP_IN_APP_ACTION); - setOptionChangedListener(MenuAction.PIP_IN_APP_ACTION); + actionList.add(MenuAction.PIP_ACTION); + setOptionChangedListener(MenuAction.PIP_ACTION); mPositionPipAction = actionList.size() - 1; actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); setOptionChangedListener(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); @@ -110,39 +106,34 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { // Case 1 PipInputManager pipInputManager = getMainActivity().getPipInputManager(); if (pipInputManager.getPipInputSize(false) < 2) { - if (mInAppPipAction) { + if (mHasPipAction) { removeAction(mPositionPipAction); - mInAppPipAction = false; - if (BuildCompat.isAtLeastN()) { - addAction(mPositionPipAction, MenuAction.SYSTEMWIDE_PIP_ACTION); - } + mHasPipAction = false; return true; } - return false; } else { - if (!mInAppPipAction) { - removeAction(mPositionPipAction); - addAction(mPositionPipAction, MenuAction.PIP_IN_APP_ACTION); - mInAppPipAction = true; + if (!mHasPipAction) { + addAction(mPositionPipAction, MenuAction.PIP_ACTION); + mHasPipAction = true; changed = true; } } // Case 2 boolean isPipEnabled = getMainActivity().isPipEnabled(); - boolean oldEnabled = MenuAction.PIP_IN_APP_ACTION.isEnabled(); + boolean oldEnabled = MenuAction.PIP_ACTION.isEnabled(); boolean newEnabled = pipInputManager.getPipInputSize(true) > 0; if (oldEnabled != newEnabled) { // Should not disable the item if the PIP is already turned on so that the user can // force exit it. if (newEnabled || !isPipEnabled) { - MenuAction.PIP_IN_APP_ACTION.setEnabled(newEnabled); + MenuAction.PIP_ACTION.setEnabled(newEnabled); changed = true; } } // Case 3 & 4 - we just need to update the icon. - MenuAction.PIP_IN_APP_ACTION.setDrawableResId( + MenuAction.PIP_ACTION.setDrawableResId( isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off); return changed; } @@ -182,12 +173,9 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { getMainActivity().getOverlayManager().getSideFragmentManager().show( new DisplayModeFragment()); break; - case TvOptionsManager.OPTION_IN_APP_PIP: + case TvOptionsManager.OPTION_PIP: getMainActivity().togglePipView(); break; - case TvOptionsManager.OPTION_SYSTEMWIDE_PIP: - getMainActivity().enterPictureInPictureMode(); - break; case TvOptionsManager.OPTION_MULTI_AUDIO: getMainActivity().getOverlayManager().getSideFragmentManager().show( new MultiAudioFragment()); diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java index 0685d14b..3ae80597 100644 --- a/src/com/android/tv/onboarding/OnboardingActivity.java +++ b/src/com/android/tv/onboarding/OnboardingActivity.java @@ -73,55 +73,48 @@ public class OnboardingActivity extends SetupActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!PermissionUtils.hasAccessAllEpg(this)) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show(); - finish(); - return; - } else if (checkSelfPermission(PERMISSION_READ_TV_LISTINGS) - != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, - PERMISSIONS_REQUEST_READ_TV_LISTINGS); - } + // Make the channels of the new inputs which have been setup outside Live TV + // browsable. + mChannelDataManager = TvApplication.getSingletons(this).getChannelDataManager(); + if (mChannelDataManager.isDbLoadFinished()) { + SetupUtils.getInstance(this).markNewChannelsBrowsable(); + } else { + mChannelDataManager.addListener(mChannelListener); } } @Override protected void onDestroy() { - if (mChannelDataManager != null) { - mChannelDataManager.removeListener(mChannelListener); - } + mChannelDataManager.removeListener(mChannelListener); super.onDestroy(); } @Override protected Fragment onCreateInitialFragment() { - if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) { - // Make the channels of the new inputs which have been setup outside Live TV - // browsable. - mChannelDataManager = TvApplication.getSingletons(this).getChannelDataManager(); - if (mChannelDataManager.isDbLoadFinished()) { - SetupUtils.getInstance(this).markNewChannelsBrowsable(); - } else { - mChannelDataManager.addListener(mChannelListener); + return OnboardingUtils.isFirstRunWithCurrentVersion(this) ? new WelcomeFragment() + : new SetupSourcesFragment(); + } + + @Override + protected void onResume() { + super.onResume(); + if (!PermissionUtils.hasAccessAllEpg(this)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show(); + finish(); + } else if (checkSelfPermission(PERMISSION_READ_TV_LISTINGS) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, + PERMISSIONS_REQUEST_READ_TV_LISTINGS); } - return OnboardingUtils.isFirstRunWithCurrentVersion(this) ? new WelcomeFragment() - : new SetupSourcesFragment(); } - return null; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) { - if (grantResults != null && grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - finish(); - Intent intentForNextActivity = getIntent().getParcelableExtra( - KEY_INTENT_AFTER_COMPLETION); - startActivity(buildIntent(this, intentForNextActivity)); - } else { + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, Toast.LENGTH_LONG).show(); finish(); diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java index 23145503..ebf32d00 100644 --- a/src/com/android/tv/onboarding/SetupSourcesFragment.java +++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java @@ -30,6 +30,7 @@ import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.GuidedActionsStylist; import android.support.v17.leanback.widget.VerticalGridView; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -59,14 +60,16 @@ import java.util.List; * A fragment for channel source info/setup. */ public class SetupSourcesFragment extends SetupMultiPaneFragment { - private static final String TAG = "SetupSourcesFragment"; - public static final String ACTION_CATEGORY = "com.android.tv.onboarding.SetupSourcesFragment"; public static final int ACTION_PLAY_STORE = 1; + public static final int DEFAULT_THEME = -1; + private static final String SETUP_TRACKER_LABEL = "Setup fragment"; + private static int sTheme = DEFAULT_THEME; + private InputSetupRunnable mInputSetupRunnable; private ContentFragment mContentFragment; @@ -74,7 +77,12 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); + LayoutInflater localInflater = inflater; + if (sTheme != -1) { + ContextThemeWrapper themeWrapper = new ContextThemeWrapper(getActivity(), sTheme); + localInflater = inflater.cloneInContext(themeWrapper); + } + View view = super.onCreateView(localInflater, container, savedInstanceState); TvApplication.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL); return view; } @@ -89,10 +97,10 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { @Override protected SetupGuidedStepFragment onCreateContentFragment() { mContentFragment = new ContentFragment(); - mContentFragment.setParentFragment(this); Bundle arguments = new Bundle(); arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true); mContentFragment.setArguments(arguments); + mContentFragment.setParentFragment(this); return mContentFragment; } @@ -102,6 +110,13 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { } /** + * Sets the custom theme dynamically. + */ + public static void setTheme(int theme) { + sTheme = theme; + } + + /** * Call this method to run customized input setup. * * @param runnable runnable to be called when the input setup is necessary. @@ -158,11 +173,6 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { handleInputChanged(); } - @Override - public void onInputUpdated(String inputId) { - handleInputChanged(); - } - private void handleInputChanged() { // The actions created while enter transition is running will not be included in the // fragment transition. @@ -385,6 +395,11 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { updateActions(); } + @Override + public int onProvideTheme() { + return sTheme == DEFAULT_THEME ? super.onProvideTheme() : sTheme; + } + void executePendingAction() { switch (mPendingAction) { case PENDING_ACTION_INPUT_CHANGED: diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java index 00f7fe8d..ed85df68 100644 --- a/src/com/android/tv/onboarding/WelcomeFragment.java +++ b/src/com/android/tv/onboarding/WelcomeFragment.java @@ -20,11 +20,8 @@ import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v17.leanback.app.OnboardingFragment; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +31,7 @@ import android.widget.ImageView; import com.android.tv.R; import com.android.tv.common.ui.setup.SetupActionHelper; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; +import com.android.tv.common.ui.setup.leanback.OnboardingFragment; import java.util.ArrayList; import java.util.List; @@ -582,6 +580,7 @@ public class WelcomeFragment extends OnboardingFragment { private ImageView mArrowView; private Animator mAnimator; + private boolean mNeedToEndAnimator; public WelcomeFragment() { setExitTransition(new SetupAnimationHelper.TransitionBuilder() @@ -590,63 +589,16 @@ public class WelcomeFragment extends OnboardingFragment { .build()); } - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - initialize(); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - initialize(); - } - - private void initialize() { - if (mPageTitles == null) { - mPageTitles = getResources().getStringArray(R.array.welcome_page_titles); - mPageDescriptions = getResources().getStringArray(R.array.welcome_page_descriptions); - } - } - @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - setLogoResourceId(R.drawable.splash_logo); - if (savedInstanceState != null) { - switch (getCurrentPageIndex()) { - case 0: - mTvContentView.setImageResource( - TV_FRAMES_1_START[TV_FRAMES_1_START.length - 1]); - break; - case 1: - mTvContentView.setImageResource( - TV_FRAMES_2_START[TV_FRAMES_2_START.length - 1]); - break; - case 2: - mTvContentView.setImageResource( - TV_FRAMES_3_ORANGE_START[TV_FRAMES_3_ORANGE_START.length - 1]); - mArrowView.setImageResource(TV_FRAMES_3_BLUE_ARROW[0]); - break; - case 3: - default: - mTvContentView.setImageResource( - TV_FRAMES_4_START[TV_FRAMES_4_START.length - 1]); - break; - } - } - return view; - } - - @Override - public int onProvideTheme() { - return R.style.Theme_Leanback_Onboarding; + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mPageTitles = getResources().getStringArray(R.array.welcome_page_titles); + mPageDescriptions = getResources().getStringArray(R.array.welcome_page_descriptions); + return super.onCreateView(inflater, container, savedInstanceState); } @Override - protected Animator onCreateEnterAnimation() { + protected void onStartEnterAnimation() { List<Animator> animators = new ArrayList<>(); // Cloud 1 View view = getActivity().findViewById(R.id.cloud1); @@ -688,7 +640,9 @@ public class WelcomeFragment extends OnboardingFragment { animators.add(animator); AnimatorSet set = new AnimatorSet(); set.playTogether(animators); - return set; + mAnimator = set; + mAnimator.start(); + mNeedToEndAnimator = true; } @Nullable @@ -729,14 +683,23 @@ public class WelcomeFragment extends OnboardingFragment { } @Override + protected int getLogoResourceId() { + return R.drawable.splash_logo; + } + + @Override protected void onFinishFragment() { SetupActionHelper.onActionClick(WelcomeFragment.this, ACTION_CATEGORY, ACTION_NEXT); } @Override - protected void onPageChanged(int newPage, int previousPage) { + protected void onStartPageChangeAnimation(int previousPage) { if (mAnimator != null) { - mAnimator.cancel(); + if (mNeedToEndAnimator) { + mAnimator.end(); + } else { + mAnimator.cancel(); + } } mArrowView.setVisibility(View.GONE); // TV screen hiding animator. @@ -747,7 +710,7 @@ public class WelcomeFragment extends OnboardingFragment { // TV screen showing animator. AnimatorSet animatorSet = new AnimatorSet(); int firstFrame; - switch (newPage) { + switch (getCurrentPageIndex()) { case 0: animatorSet.playSequentially(hideAnimator, SetupAnimationHelper.createFrameAnimator(mTvContentView, @@ -799,5 +762,6 @@ public class WelcomeFragment extends OnboardingFragment { }); mAnimator = SetupAnimationHelper.applyAnimationTimeScale(animatorSet); mAnimator.start(); + mNeedToEndAnimator = false; } } diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java index 6b5d6635..6c00ee11 100644 --- a/src/com/android/tv/parental/ContentRatingSystem.java +++ b/src/com/android/tv/parental/ContentRatingSystem.java @@ -490,19 +490,6 @@ public class ContentRatingSystem { mRatingOrder = ratingOrder; } - /** - * Returns index of the rating in this order. - * Returns -1 if this order doesn't contain the rating. - */ - public int getRatingIndex(Rating rating) { - for (int i = 0; i < mRatingOrder.size(); i++) { - if (mRatingOrder.get(i).getName().equals(rating.getName())) { - return i; - } - } - return -1; - } - public static class Builder { private final List<String> mRatingNames = new ArrayList<>(); diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java index da88f70d..3cd6186c 100644 --- a/src/com/android/tv/receiver/BootCompletedReceiver.java +++ b/src/com/android/tv/receiver/BootCompletedReceiver.java @@ -21,7 +21,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.support.v4.os.BuildCompat; import android.util.Log; import com.android.tv.Features; @@ -73,7 +72,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { } } - if (CommonFeatures.DVR.isEnabled(context) && BuildCompat.isAtLeastN()) { + // DVR + if (CommonFeatures.DVR.isEnabled(context)) { DvrRecordingService.startService(context); } } diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java index 2e19c089..bd81cee3 100644 --- a/src/com/android/tv/receiver/GlobalKeyReceiver.java +++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java @@ -19,7 +19,6 @@ package com.android.tv.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.media.tv.TvContract; import android.util.Log; import android.view.KeyEvent; @@ -40,22 +39,10 @@ public class GlobalKeyReceiver extends BroadcastReceiver { if (DEBUG) Log.d(TAG, "onReceive: " + event); int keyCode = event.getKeyCode(); int action = event.getAction(); - if (action == KeyEvent.ACTION_UP) { - switch (keyCode) { - case KeyEvent.KEYCODE_GUIDE: - context.startActivity( - new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI)); - break; - case KeyEvent.KEYCODE_TV: - ((TvApplication) context.getApplicationContext()).handleTvKey(); - break; - case KeyEvent.KEYCODE_TV_INPUT: - ((TvApplication) context.getApplicationContext()).handleTvInputKey(); - break; - default: - // Do nothing - break; - } + if (keyCode == KeyEvent.KEYCODE_TV && action == KeyEvent.ACTION_UP) { + ((TvApplication) context.getApplicationContext()).handleTvKey(); + } else if (keyCode == KeyEvent.KEYCODE_TV_INPUT && action == KeyEvent.ACTION_UP) { + ((TvApplication) context.getApplicationContext()).handleTvInputKey(); } } } diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java index 4c850402..67f0529f 100644 --- a/src/com/android/tv/receiver/PackageIntentsReceiver.java +++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java @@ -17,18 +17,59 @@ package com.android.tv.receiver; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import com.android.tv.TvActivity; import com.android.tv.TvApplication; +import com.android.usbtuner.setup.TunerSetupActivity; +import com.android.usbtuner.UsbTunerPreferences; +import com.android.usbtuner.tvinput.UsbTunerTvInputService; /** * A class for handling the broadcast intents from PackageManager. */ public class PackageIntentsReceiver extends BroadcastReceiver { + private PackageManager mPackageManager; + private ComponentName mTvActivityComponentName; + private ComponentName mUsbTunerComponentName; + + private void init(Context context) { + mPackageManager = context.getPackageManager(); + mTvActivityComponentName = new ComponentName(context, TvActivity.class); + mUsbTunerComponentName = new ComponentName(context, UsbTunerTvInputService.class); + } @Override public void onReceive(Context context, Intent intent) { + if (mPackageManager == null) { + init(context); + } ((TvApplication) context.getApplicationContext()).handleInputCountChanged(); + // Check the component status of UsbTunerTvInputService and TvActivity here to make sure + // start the setup activity of USB tuner TV input service only when those components are + // enabled. + if (UsbTunerPreferences.shouldShowSetupActivity(context) + && Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) + && mPackageManager.getComponentEnabledSetting(mTvActivityComponentName) + == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + && mPackageManager.getComponentEnabledSetting(mUsbTunerComponentName) + == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + startUsbTunerSetupActivity(context); + UsbTunerPreferences.setShouldShowSetupActivity(context, false); + } + } + + /** + * Launches the setup activity of USB tuner TV input service. + * + * @param context {@link Context} instance + */ + private static void startUsbTunerSetupActivity(Context context) { + Intent intent = TunerSetupActivity.createSetupActivity(context); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } } diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index 0095482d..c6a0c3f6 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -28,7 +28,6 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.media.tv.TvInputInfo; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -52,7 +51,6 @@ import com.android.tv.data.Program; import com.android.tv.util.BitmapUtils; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; import com.android.tv.util.ImageLoader; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -129,12 +127,6 @@ public class NotificationService extends Service implements Recommender.Listener public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M - && !PermissionUtils.hasAccessAllEpg(this)) { - Log.w(TAG, "Live TV requires the system permission on this platform."); - stopSelf(); - return; - } mCurrentNotificationCount = 0; mNotificationChannels = new long[NOTIFICATION_COUNT]; diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index a7d4c46d..66dd9fe4 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -16,6 +16,7 @@ package com.android.tv.recommendation; +import android.content.ContentUris; import android.content.Context; import android.content.UriMatcher; import android.database.ContentObserver; @@ -34,10 +35,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import com.android.tv.TvApplication; import com.android.tv.common.WeakHandler; import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.data.WatchedHistoryManager; import com.android.tv.util.PermissionUtils; @@ -52,6 +51,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class RecommendationDataManager implements WatchedHistoryManager.Listener { + private static final String TAG = "RecommendationDataManager"; + private static final UriMatcher sUriMatcher; private static final int MATCH_CHANNEL = 1; private static final int MATCH_CHANNEL_ID = 2; @@ -65,15 +66,19 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private static final int MSG_START = 1000; private static final int MSG_STOP = 1001; - private static final int MSG_UPDATE_CHANNELS = 1002; - private static final int MSG_UPDATE_WATCH_HISTORY = 1003; - private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED = 1004; - private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED = 1005; + private static final int MSG_UPDATE_CHANNEL = 1002; + private static final int MSG_UPDATE_CHANNELS = 1003; + private static final int MSG_UPDATE_WATCH_HISTORY = 1004; + private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED = 1005; + private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED = 1006; private static final int MSG_FIRST = MSG_START; private static final int MSG_LAST = MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED; + private static final int INVALID_INDEX = -1; + private static RecommendationDataManager sManager; + private final static Object sListenerLock = new Object(); private final ContentObserver mContentObserver; private final Map<Long, ChannelRecord> mChannelRecordMap = new ConcurrentHashMap<>(); private final Map<Long, ChannelRecord> mAvailableChannelRecordMap = new ConcurrentHashMap<>(); @@ -93,33 +98,10 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private final HandlerThread mHandlerThread; private final Handler mHandler; - private final Handler mMainHandler; @Nullable private WatchedHistoryManager mWatchedHistoryManager; - private final ChannelDataManager mChannelDataManager; - private final ChannelDataManager.Listener mChannelDataListener = - new ChannelDataManager.Listener() { - @Override - @MainThread - public void onLoadFinished() { - updateChannelData(); - } - @Override - @MainThread - public void onChannelListUpdated() { - updateChannelData(); - } - - @Override - @MainThread - public void onChannelBrowsableChanged() { - updateChannelData(); - } - }; - - // For thread safety, this variable is handled only on main thread. - private final List<Listener> mListeners = new ArrayList<>(); + private final List<ListenerRecord> mListeners = new ArrayList<>(); /** * Gets instance of RecommendationDataManager, and adds a {@link Listener}. @@ -130,11 +112,25 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener public synchronized static RecommendationDataManager acquireManager( Context context, @NonNull Listener listener) { if (sManager == null) { - sManager = new RecommendationDataManager(context, listener); + sManager = new RecommendationDataManager(context); } + sManager.addListener(listener); + sManager.start(); return sManager; } + /** + * Removes the {@link Listener}, and releases RecommendationDataManager + * if there are no listeners remained. + */ + public void release(@NonNull Listener listener) { + removeListener(listener); + synchronized (sListenerLock) { + if (mListeners.size() == 0) { + stop(); + } + } + } private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override @@ -191,37 +187,12 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener public void onInputUpdated(String inputId) { } }; - private RecommendationDataManager(Context context, final Listener listener) { + private RecommendationDataManager(Context context) { mContext = context.getApplicationContext(); mHandlerThread = new HandlerThread("RecommendationDataManager"); mHandlerThread.start(); mHandler = new RecommendationHandler(mHandlerThread.getLooper(), this); - mMainHandler = new RecommendationMainHandler(Looper.getMainLooper(), this); mContentObserver = new RecommendationContentObserver(mHandler); - mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager(); - runOnMainThread(new Runnable() { - @Override - public void run() { - addListener(listener); - start(); - } - }); - } - - /** - * Removes the {@link Listener}, and releases RecommendationDataManager - * if there are no listeners remained. - */ - public void release(@NonNull final Listener listener) { - runOnMainThread(new Runnable() { - @Override - public void run() { - removeListener(listener); - if (mListeners.size() == 0) { - stop(); - } - } - }); } /** @@ -245,48 +216,54 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener return Collections.unmodifiableCollection(mAvailableChannelRecordMap.values()); } - @MainThread private void start() { mHandler.sendEmptyMessage(MSG_START); - mChannelDataManager.addListener(mChannelDataListener); - if (mChannelDataManager.isDbLoadFinished()) { - updateChannelData(); - } } - @MainThread private void stop() { for (int what = MSG_FIRST; what <= MSG_LAST; ++what) { mHandler.removeMessages(what); } - mChannelDataManager.removeListener(mChannelDataListener); mHandler.sendEmptyMessage(MSG_STOP); mHandlerThread.quitSafely(); - mMainHandler.removeCallbacksAndMessages(null); sManager = null; } - @MainThread - private void updateChannelData() { - mHandler.removeMessages(MSG_UPDATE_CHANNELS); - mHandler.obtainMessage(MSG_UPDATE_CHANNELS, mChannelDataManager.getBrowsableChannelList()) - .sendToTarget(); + private int getListenerIndexLocked(Listener listener) { + for (int i = 0; i < mListeners.size(); ++i) { + if (mListeners.get(i).mListener == listener) { + return i; + } + } + return INVALID_INDEX; } - @MainThread private void addListener(Listener listener) { - mListeners.add(listener); + synchronized (sListenerLock) { + if (getListenerIndexLocked(listener) == INVALID_INDEX) { + mListeners.add((new ListenerRecord(listener))); + } + } } - @MainThread private void removeListener(Listener listener) { - mListeners.remove(listener); + synchronized (sListenerLock) { + int idx = getListenerIndexLocked(listener); + if (idx != INVALID_INDEX) { + ListenerRecord record = mListeners.remove(idx); + record.mListener = null; + } + } } private void onStart() { if (!mStarted) { mStarted = true; mCancelLoadTask = false; + mContext.getContentResolver().registerContentObserver( + TvContract.Channels.CONTENT_URI, true, mContentObserver); + mHandler.obtainMessage(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI) + .sendToTarget(); if (!PermissionUtils.hasAccessWatchedHistory(mContext)) { mWatchedHistoryManager = new WatchedHistoryManager(mContext); mWatchedHistoryManager.setListener(this); @@ -320,7 +297,42 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } @WorkerThread - private void onUpdateChannels(List<Channel> channels) { + private void onUpdateChannel(Uri uri) { + Channel channel = null; + try (Cursor cursor = mContext.getContentResolver().query(uri, Channel.PROJECTION, + null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + channel = Channel.fromCursor(cursor); + } + } + boolean isChannelRecordMapChanged = false; + if (channel == null) { + long channelId = ContentUris.parseId(uri); + mChannelRecordMap.remove(channelId); + isChannelRecordMapChanged = mAvailableChannelRecordMap.remove(channelId) != null; + } else if (updateChannelRecordMapFromChannel(channel)) { + isChannelRecordMapChanged = true; + } + if (isChannelRecordMapChanged && mChannelRecordMapLoaded + && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) { + mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED); + } + } + + @WorkerThread + private void onUpdateChannels(Uri uri) { + List<Channel> channels = new ArrayList<>(); + try (Cursor cursor = mContext.getContentResolver().query(uri, Channel.PROJECTION, + null, null, null)) { + if (cursor != null) { + while (cursor.moveToNext()) { + if (mCancelLoadTask) { + return; + } + channels.add(Channel.fromCursor(cursor)); + } + } + } boolean isChannelRecordMapChanged = false; Set<Long> removedChannelIdSet = new HashSet<>(mChannelRecordMap.keySet()); // Builds removedChannelIdSet. @@ -362,14 +374,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram(watchedProgram); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } + synchronized (sListenerLock) { + for (ListenerRecord l : mListeners) { + l.postNewWatchLog(channelRecord); } - }); + } } } if (!mChannelRecordMapLoaded) { @@ -401,17 +410,14 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener @Override public void onNewRecordAdded(WatchedHistoryManager.WatchedRecord watchedRecord) { - final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram( + ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram( convertFromWatchedHistoryManagerRecords(watchedRecord)); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } + synchronized (sListenerLock) { + for (ListenerRecord l : mListeners) { + l.postNewWatchLog(channelRecord); } - }); + } } } @@ -446,25 +452,19 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private void onNotifyChannelRecordMapLoaded() { mChannelRecordMapLoaded = true; - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordLoaded(); - } + synchronized (sListenerLock) { + for (ListenerRecord l : mListeners) { + l.postChannelRecordLoaded(); } - }); + } } private void onNotifyChannelRecordMapChanged() { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordChanged(); - } + synchronized (sListenerLock) { + for (ListenerRecord l : mListeners) { + l.postChannelRecordChanged(); } - }); + } } /** @@ -511,6 +511,15 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener @Override public void onChange(final boolean selfChange, final Uri uri) { switch (sUriMatcher.match(uri)) { + case MATCH_CHANNEL: + if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI)) { + mHandler.obtainMessage(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI) + .sendToTarget(); + } + break; + case MATCH_CHANNEL_ID: + mHandler.obtainMessage(MSG_UPDATE_CHANNEL, uri).sendToTarget(); + break; case MATCH_WATCHED_PROGRAM_ID: if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)) { @@ -521,14 +530,6 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } - private void runOnMainThread(Runnable r) { - if (Looper.myLooper() == Looper.getMainLooper()) { - r.run(); - } else { - mMainHandler.post(r); - } - } - /** * A listener interface to receive notification about the recommendation data. * @@ -560,6 +561,55 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener void onChannelRecordChanged(); } + private static class ListenerRecord { + private Listener mListener; + private final Handler mHandler; + + public ListenerRecord(Listener listener) { + mHandler = new Handler(); + mListener = listener; + } + + public void postChannelRecordLoaded() { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (sListenerLock) { + if (mListener != null) { + mListener.onChannelRecordLoaded(); + } + } + } + }); + } + + public void postNewWatchLog(final ChannelRecord channelRecord) { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (sListenerLock) { + if (mListener != null) { + mListener.onNewWatchLog(channelRecord); + } + } + } + }); + } + + public void postChannelRecordChanged() { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (sListenerLock) { + if (mListener != null) { + mListener.onChannelRecordChanged(); + } + } + } + }); + } + } + private static class RecommendationHandler extends WeakHandler<RecommendationDataManager> { public RecommendationHandler(@NonNull Looper looper, RecommendationDataManager ref) { super(looper, ref); @@ -576,9 +626,14 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener dataManager.onStop(); } break; + case MSG_UPDATE_CHANNEL: + if (dataManager.mStarted) { + dataManager.onUpdateChannel((Uri) msg.obj); + } + break; case MSG_UPDATE_CHANNELS: if (dataManager.mStarted) { - dataManager.onUpdateChannels((List<Channel>) msg.obj); + dataManager.onUpdateChannels((Uri) msg.obj); } break; case MSG_UPDATE_WATCH_HISTORY: @@ -599,13 +654,4 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } } - - private static class RecommendationMainHandler extends WeakHandler<RecommendationDataManager> { - public RecommendationMainHandler(@NonNull Looper looper, RecommendationDataManager ref) { - super(looper, ref); - } - - @Override - protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) { } - } } diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java index 82c2893d..0561449e 100644 --- a/src/com/android/tv/recommendation/Recommender.java +++ b/src/com/android/tv/recommendation/Recommender.java @@ -145,7 +145,7 @@ public class Recommender implements RecommendationDataManager.Listener { mChannelSortKey.put(records.get(i).first.getId(), String.format(sortKeyFormat, i)); results.add(records.get(i).first); } - return results; + return Collections.unmodifiableList(results); } /** diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java index 5ff7cae9..694da6bf 100644 --- a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java +++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java @@ -16,9 +16,7 @@ package com.android.tv.recommendation; -import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; import com.android.tv.data.Program; @@ -38,6 +36,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { private static final double TIME_MATCH_WEIGHT = 1 - TITLE_MATCH_WEIGHT; private static final long DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(14); private static final long MAX_DIFF_MS_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(56); + private static final String REGULAR_EXPRESSION_FOR_WHITE_SPACES = "\\s+"; @Override public double evaluateChannel(long channelId) { @@ -92,8 +91,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { return maxScore; } - private static double calculateRoutineWatchScore(Program currentProgram, Program watchedProgram, - long watchedDurationMs) { + private double calculateRoutineWatchScore( + Program currentProgram, Program watchedProgram, long watchedDurationMs) { double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram); double titleMatchScore = calculateTitleMatchScore( currentProgram.getTitle(), watchedProgram.getTitle()); @@ -108,16 +107,10 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { * watchDurationScore * multiplierForOldProgram; } - @VisibleForTesting - static double calculateTitleMatchScore(@Nullable String title1, @Nullable String title2) { - if (TextUtils.isEmpty(title1) || TextUtils.isEmpty(title2)) { - return 0; - } + private double calculateTitleMatchScore(String title1, String title2) { List<String> wordList1 = splitTextToWords(title1); List<String> wordList2 = splitTextToWords(title2); - if (wordList1.isEmpty() || wordList2.isEmpty()) { - return 0; - } + int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength( wordList1, wordList2); @@ -128,8 +121,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } @VisibleForTesting - static int calculateMaximumMatchedWordSequenceLength(List<String> toSearchWords, - List<String> toMatchWords) { + int calculateMaximumMatchedWordSequenceLength( + List<String> toSearchWords, List<String> toMatchWords) { int[] matchedWordSeqLen = new int[toMatchWords.size()]; int maxMatchedWordSeqLen = 0; for (String word : toSearchWords) { @@ -149,7 +142,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { return maxMatchedWordSeqLen; } - private static double calculateTimeMatchScore(Program p1, Program p2) { + private double calculateTimeMatchScore(Program p1, Program p2) { ProgramTime t1 = ProgramTime.createFromProgram(p1); ProgramTime t2 = ProgramTime.createFromProgram(p2); @@ -162,7 +155,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } @VisibleForTesting - static double calculateOverlappedIntervalScore(ProgramTime t1, ProgramTime t2) { + double calculateOverlappedIntervalScore(ProgramTime t1, ProgramTime t2) { if (t1.dayChanged && !t2.dayChanged) { // Swap two values. return calculateOverlappedIntervalScore(t2, t1); @@ -188,7 +181,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { return score; } - private static double calculateWatchDurationScore(Program program, long durationMs) { + private double calculateWatchDurationScore(Program program, long durationMs) { return (double) durationMs / (program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis()); } diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java index c7b94a15..befa004c 100644 --- a/src/com/android/tv/ui/AppLayerTvView.java +++ b/src/com/android/tv/ui/AppLayerTvView.java @@ -16,8 +16,9 @@ package com.android.tv.ui; +import com.android.tv.common.recording.PlaybackTvView; + import android.content.Context; -import android.media.tv.TvView; import android.util.AttributeSet; /** @@ -29,7 +30,7 @@ import android.util.AttributeSet; * TODO: remove this class once the TvView.setMain() is revisited. * </p> */ -public class AppLayerTvView extends TvView { +public class AppLayerTvView extends PlaybackTvView { public AppLayerTvView(Context context) { super(context); } diff --git a/src/com/android/tv/ui/BlockScreenView.java b/src/com/android/tv/ui/BlockScreenView.java deleted file mode 100644 index 52b9389d..00000000 --- a/src/com/android/tv/ui/BlockScreenView.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2015 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.android.tv.ui; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorInflater; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.ui.TunableTvView.BlockScreenType; - -public class BlockScreenView extends LinearLayout { - private View mContainerView; - private View mImageContainer; - private ImageView mNormalImageView; - private ImageView mShrunkenImageView; - private View mSpace; - private TextView mTextView; - - private final int mSpacingNormal; - private final int mSpacingShrunken; - - // Animators used for fade in/out of block screen icon. - private Animator mFadeIn; - private Animator mFadeOut; - - public BlockScreenView(Context context) { - this(context, null, 0); - } - - public BlockScreenView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BlockScreenView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mSpacingNormal = getResources().getDimensionPixelOffset( - R.dimen.tvview_block_vertical_spacing); - mSpacingShrunken = getResources().getDimensionPixelOffset( - R.dimen.shrunken_tvview_block_vertical_spacing); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContainerView = findViewById(R.id.block_screen_container); - mImageContainer = findViewById(R.id.image_container); - mNormalImageView = (ImageView) findViewById(R.id.block_screen_icon); - mShrunkenImageView = (ImageView) findViewById(R.id.block_screen_shrunken_icon); - mSpace = findViewById(R.id.space); - mTextView = (TextView) findViewById(R.id.block_screen_text); - mFadeIn = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_in); - mFadeIn.setTarget(mContainerView); - mFadeOut = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_out); - mFadeOut.setTarget(mContainerView); - mFadeOut.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainerView.setVisibility(GONE); - mContainerView.setAlpha(1f); - } - }); - } - - /** - * Sets the normal image. - */ - public void setImage(int resId) { - mNormalImageView.setImageResource(resId); - updateSpaceVisibility(); - } - - /** - * Sets the scale type of the normal image. - */ - public void setScaleType(ScaleType scaleType) { - mNormalImageView.setScaleType(scaleType); - updateSpaceVisibility(); - } - - /** - * Sets the shrunken image. - */ - public void setShrunkenImage(int resId) { - mShrunkenImageView.setImageResource(resId); - updateSpaceVisibility(); - } - - /** - * Show or hide the image of this view. - */ - public void setImageVisibility(boolean visible) { - mImageContainer.setVisibility(visible ? VISIBLE : GONE); - updateSpaceVisibility(); - } - - /** - * Sets the text message. - */ - public void setText(int resId) { - mTextView.setText(resId); - updateSpaceVisibility(); - } - - /** - * Sets the text message. - */ - public void setText(String text) { - mTextView.setText(text); - updateSpaceVisibility(); - } - - private void updateSpaceVisibility() { - if (isImageViewVisible() && isTextViewVisible(mTextView)) { - mSpace.setVisibility(VISIBLE); - } else { - mSpace.setVisibility(GONE); - } - } - - private boolean isImageViewVisible() { - return mImageContainer.getVisibility() == VISIBLE - && (isImageViewVisible(mNormalImageView) || isImageViewVisible(mShrunkenImageView)); - } - - private static boolean isImageViewVisible(ImageView imageView) { - return imageView.getVisibility() != GONE && imageView.getDrawable() != null; - } - - private static boolean isTextViewVisible(TextView textView) { - return textView.getVisibility() != GONE && !TextUtils.isEmpty(textView.getText()); - } - - /** - * Changes the spacing between the image view and the text view according to the - * {@code blockScreenType}. - */ - public void setSpacing(@BlockScreenType int blockScreenType) { - mSpace.getLayoutParams().height = - blockScreenType == TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW - ? mSpacingShrunken : mSpacingNormal; - requestLayout(); - } - - /** - * Changes the view layout according to the {@code blockScreenType}. - */ - public void onBlockStatusChanged(@BlockScreenType int blockScreenType, boolean withAnimation) { - if (!withAnimation) { - switch (blockScreenType) { - case TunableTvView.BLOCK_SCREEN_TYPE_NO_UI: - mContainerView.setVisibility(GONE); - break; - case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: - mNormalImageView.setVisibility(GONE); - mShrunkenImageView.setVisibility(VISIBLE); - mContainerView.setVisibility(VISIBLE); - break; - case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL: - mNormalImageView.setVisibility(VISIBLE); - mShrunkenImageView.setVisibility(GONE); - mContainerView.setVisibility(VISIBLE); - break; - } - } else { - switch (blockScreenType) { - case TunableTvView.BLOCK_SCREEN_TYPE_NO_UI: - if (mContainerView.getVisibility() == VISIBLE) { - mFadeOut.start(); - } - break; - case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: - mNormalImageView.setVisibility(GONE); - mShrunkenImageView.setVisibility(VISIBLE); - mContainerView.setVisibility(VISIBLE); - if (mContainerView.getVisibility() == GONE) { - mFadeIn.start(); - } - break; - case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL: - mNormalImageView.setVisibility(VISIBLE); - mShrunkenImageView.setVisibility(GONE); - mContainerView.setVisibility(VISIBLE); - if (mContainerView.getVisibility() == GONE) { - mFadeIn.start(); - } - break; - } - } - updateSpaceVisibility(); - } - - /** - * Scales the contents view by the given {@code scale}. - */ - public void scaleContainerView(float scale) { - mContainerView.setScaleX(scale); - mContainerView.setScaleY(scale); - } - - public void addFadeOutAnimationListener(AnimatorListener listener) { - mFadeOut.addListener(listener); - } - - /** - * Ends the currently running animations. - */ - public void endAnimations() { - if (mFadeIn != null && mFadeIn.isRunning()) { - mFadeIn.end(); - } - if (mFadeOut != null && mFadeOut.isRunning()) { - mFadeOut.end(); - } - } -} diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java index a36ba83c..17ac8f3b 100644 --- a/src/com/android/tv/ui/ChannelBannerView.java +++ b/src/com/android/tv/ui/ChannelBannerView.java @@ -16,6 +16,8 @@ package com.android.tv.ui; +import static com.android.tv.util.ImageLoader.ImageLoaderCallback; + import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; @@ -33,10 +35,8 @@ import android.support.annotation.Nullable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; -import android.text.format.DateUtils; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -50,13 +50,11 @@ import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.common.recording.RecordedProgram; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; import com.android.tv.util.ImageCache; import com.android.tv.util.ImageLoader; -import com.android.tv.util.ImageLoader.ImageLoaderCallback; import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.Utils; @@ -68,8 +66,6 @@ import java.util.Objects; * A view to render channel banner. */ public class ChannelBannerView extends FrameLayout implements TvTransitionManager.TransitionLayout { - private static final String TAG = "ChannelBannerView"; - private static final boolean DEBUG = false; /** * Show all information at the channel banner. @@ -115,7 +111,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private View mAnchorView; private Channel mCurrentChannel; private Program mLastUpdatedProgram; - private RecordedProgram mLastUpdatedRecordedProgram; private final Handler mHandler = new Handler(); private int mLockType; @@ -240,7 +235,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage @Override protected void onAttachedToWindow() { - if (DEBUG) Log.d(TAG, "onAttachedToWindow"); super.onAttachedToWindow(); getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI, true, mProgramUpdateObserver); @@ -248,7 +242,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage @Override protected void onDetachedFromWindow() { - if (DEBUG) Log.d(TAG, "onDetachedToWindow"); getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver); super.onDetachedFromWindow(); } @@ -336,6 +329,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage /** * Update channel banner view. + * Note that this only updates the channel banner contents, + * and use onBeforeShow() or onAfterHide() for showing/hiding. * * @param info A StreamInfo that includes stream information. * If it's {@code null}, only program information will be updated. @@ -347,19 +342,19 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage scheduleHide(); } mCurrentChannel = channel; + if (channel == null) { + mLastUpdatedProgram = null; + updateProgramInfo(null); + return; + } mChannelView.setVisibility(VISIBLE); if (info != null) { // If the current channels between ChannelTuner and TvView are different, // the stream information should not be seen. - updateStreamInfo(channel != null && channel.equals(info.getCurrentChannel()) ? info - : null); + updateStreamInfo(channel.equals(info.getCurrentChannel()) ? info : null); updateChannelInfo(); } - if (mMainActivity.isRecordingPlayback()) { - updateProgramInfo(mMainActivity.getPlayingRecordedProgram()); - } else { - updateProgramInfo(mMainActivity.getCurrentProgram()); - } + updateProgramInfo(mMainActivity.getCurrentProgram()); } private void updateStreamInfo(StreamInfo info) { @@ -368,7 +363,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark : EMPTY_STRING); updateText(mAspectRatioTextView, - Utils.getAspectRatioString(info.getVideoDisplayAspectRatio())); + Utils.getAspectRatioString(info.getVideoWidth(), info.getVideoHeight())); updateText(mResolutionTextView, Utils.getVideoDefinitionLevelString( mMainActivity, info.getVideoDefinitionLevel())); @@ -385,24 +380,11 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private void updateChannelInfo() { // Update static information for a channel. - String displayNumber = EMPTY_STRING; - String displayName = EMPTY_STRING; - if (mCurrentChannel != null) { - displayNumber = mCurrentChannel.getDisplayNumber(); - if (displayNumber == null) { - displayNumber = EMPTY_STRING; - } - displayName = mCurrentChannel.getDisplayName(); - if (displayName == null) { - displayName = EMPTY_STRING; - } + String displayNumber = mCurrentChannel.getDisplayNumber(); + if (displayNumber == null) { + displayNumber = EMPTY_STRING; } - if (displayNumber.isEmpty()) { - mChannelNumberTextView.setVisibility(GONE); - } else { - mChannelNumberTextView.setVisibility(VISIBLE); - } if (displayNumber.length() <= 3) { updateTextView( mChannelNumberTextView, @@ -420,9 +402,14 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage R.dimen.channel_banner_channel_number_small_margin_top); } mChannelNumberTextView.setText(displayNumber); + String displayName = mCurrentChannel.getDisplayName(); + if (displayName == null) { + displayName = EMPTY_STRING; + } mChannelNameTextView.setText(displayName); + TvInputInfo info = mMainActivity.getTvInputManagerHelper().getTvInputInfo( - getCurrentInputId()); + mCurrentChannel.getInputId()); if (info == null || !ImageLoader.loadBitmap(createTvInputLogoLoaderCallback(info, this), new LoadTvInputLogoTask(getContext(), ImageCache.getInstance(), info))) { mTvInputLogoImageView.setVisibility(View.GONE); @@ -430,24 +417,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } mChannelLogoImageView.setImageBitmap(null); mChannelLogoImageView.setVisibility(View.GONE); - if (mCurrentChannel != null) { - mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, - mChannelLogoImageViewWidth, mChannelLogoImageViewHeight, - createChannelLogoCallback(this, mCurrentChannel)); - } - } - - private String getCurrentInputId() { - Channel channel = mMainActivity.getCurrentChannel(); - if (channel != null) { - return channel.getInputId(); - } else if (mMainActivity.isRecordingPlayback()) { - RecordedProgram recordedProgram = mMainActivity.getPlayingRecordedProgram(); - if (recordedProgram != null) { - return recordedProgram.getInputId(); - } - } - return null; + mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mChannelLogoImageViewWidth, mChannelLogoImageViewHeight, + createChannelLogoCallback(this, mCurrentChannel)); } private void updateTvInputLogo(Bitmap bitmap) { @@ -460,8 +432,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) { @Override public void onBitmapLoaded(ChannelBannerView channelBannerView, Bitmap bitmap) { - if (bitmap != null && channelBannerView.mCurrentChannel != null - && info.getId().equals(channelBannerView.mCurrentChannel.getInputId())) { + if (bitmap != null && info.getId() + .equals(channelBannerView.mCurrentChannel.getInputId())) { channelBannerView.updateTvInputLogo(bitmap); } } @@ -538,7 +510,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (mLastUpdatedProgram == null || !TextUtils.equals(program.getTitle(), mLastUpdatedProgram.getTitle()) || !TextUtils.equals(program.getEpisodeDisplayTitle(getContext()), - mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) { + mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) { updateProgramTextView(program); } updateProgramTimeInfo(program); @@ -547,7 +519,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage // cancel the animation. boolean isProgramChanged = !Objects.equals(mLastUpdatedProgram, program); if (mResizeAnimator != null && isProgramChanged) { - setLastUpdatedProgram(program); + mLastUpdatedProgram = program; mProgramInfoUpdatePendingByResizing = true; mResizeAnimator.cancel(); } else if (mResizeAnimator == null) { @@ -565,73 +537,16 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } else { mProgramInfoUpdatePendingByResizing = true; } - setLastUpdatedProgram(program); - } - - private void updateProgramInfo(RecordedProgram recordedProgram) { - if (mLockType == LOCK_CHANNEL_INFO) { - updateProgramInfo(sLockedChannelProgram); - return; - } else if (recordedProgram == null) { - updateProgramInfo(sNoProgram); - return; - } - - if (mLastUpdatedRecordedProgram == null - || !TextUtils.equals(recordedProgram.getTitle(), - mLastUpdatedRecordedProgram.getTitle()) - || !TextUtils.equals(recordedProgram.getEpisodeDisplayTitle(getContext()), - mLastUpdatedRecordedProgram.getEpisodeDisplayTitle(getContext()))) { - updateProgramTextView(recordedProgram); - } - updateProgramTimeInfo(recordedProgram); - - // When the program is changed, but the previous resize animation has not ended yet, - // cancel the animation. - boolean isProgramChanged = !Objects.equals(mLastUpdatedRecordedProgram, recordedProgram); - if (mResizeAnimator != null && isProgramChanged) { - setLastUpdatedRecordedProgram(recordedProgram); - mProgramInfoUpdatePendingByResizing = true; - mResizeAnimator.cancel(); - } else if (mResizeAnimator == null) { - if (mLockType != LOCK_NONE - || TextUtils.isEmpty(recordedProgram.getShortDescription())) { - mProgramDescriptionTextView.setVisibility(GONE); - mProgramDescriptionText = ""; - } else { - mProgramDescriptionTextView.setVisibility(VISIBLE); - mProgramDescriptionText = recordedProgram.getShortDescription(); - } - String description = mProgramDescriptionTextView.getText().toString(); - boolean needFadeAnimation = isProgramChanged - || !description.equals(mProgramDescriptionText); - updateBannerHeight(needFadeAnimation); - } else { - mProgramInfoUpdatePendingByResizing = true; - } - setLastUpdatedRecordedProgram(recordedProgram); + mLastUpdatedProgram = program; } private void updateProgramTextView(Program program) { if (program == null) { return; } - updateProgramTextView(program == sLockedChannelProgram, program.getTitle(), - program.getEpisodeTitle(), program.getEpisodeDisplayTitle(getContext())); - } - - private void updateProgramTextView(RecordedProgram recordedProgram) { - if (recordedProgram == null) { - return; - } - updateProgramTextView(false, recordedProgram.getTitle(), recordedProgram.getEpisodeTitle(), - recordedProgram.getEpisodeDisplayTitle(getContext())); - } - private void updateProgramTextView(boolean dimText, String title, String episodeTitle, - String episodeDisplayTitle) { mProgramTextView.setVisibility(View.VISIBLE); - if (dimText) { + if (program == sLockedChannelProgram) { mProgramTextView.setTextColor(mChannelBannerDimTextColor); } else { mProgramTextView.setTextColor(mChannelBannerTextColor); @@ -639,15 +554,17 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage updateTextView(mProgramTextView, R.dimen.channel_banner_program_large_text_size, R.dimen.channel_banner_program_large_margin_top); - if (TextUtils.isEmpty(episodeTitle)) { - mProgramTextView.setText(title); + if (TextUtils.isEmpty(program.getEpisodeTitle())) { + mProgramTextView.setText(program.getTitle()); } else { - String fullTitle = title + " " + episodeDisplayTitle; + String title = program.getTitle(); + String episodeTitle = program.getEpisodeDisplayTitle(getContext()); + String fullTitle = title + " " + episodeTitle; SpannableString text = new SpannableString(fullTitle); text.setSpan(new TextAppearanceSpan(getContext(), R.style.text_appearance_channel_banner_episode_title), - fullTitle.length() - episodeDisplayTitle.length(), fullTitle.length(), + fullTitle.length() - episodeTitle.length(), fullTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mProgramTextView.setText(text); } @@ -700,38 +617,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } } - private void updateProgramTimeInfo(RecordedProgram recordedProgram) { - long durationMs = recordedProgram.getDurationMillis(); - if (mLockType != LOCK_CHANNEL_INFO && durationMs > 0) { - mProgramTimeTextView.setVisibility(View.VISIBLE); - mRemainingTimeView.setVisibility(View.VISIBLE); - - mProgramTimeTextView.setText(DateUtils.formatElapsedTime(durationMs / 1000)); - - long currTimeMs = mMainActivity.getCurrentPlayingPosition(); - if (currTimeMs <= 0) { - mRemainingTimeView.setProgress(0); - } else if (currTimeMs >= durationMs) { - mRemainingTimeView.setProgress(100); - } else { - mRemainingTimeView.setProgress((int) (100 * currTimeMs / durationMs)); - } - } else { - mProgramTimeTextView.setVisibility(View.GONE); - mRemainingTimeView.setVisibility(View.GONE); - } - } - - private void setLastUpdatedProgram(Program program) { - mLastUpdatedProgram = program; - mLastUpdatedRecordedProgram = null; - } - - private void setLastUpdatedRecordedProgram(RecordedProgram recordedProgram) { - mLastUpdatedProgram = null; - mLastUpdatedRecordedProgram = recordedProgram; - } - private void updateBannerHeight(boolean needFadeAnimation) { Assert.assertNull(mResizeAnimator); // Need to measure the layout height with the new description text. diff --git a/src/com/android/tv/ui/DialogUtils.java b/src/com/android/tv/ui/DialogUtils.java deleted file mode 100644 index acbaf8c8..00000000 --- a/src/com/android/tv/ui/DialogUtils.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 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.android.tv.ui; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; - -import com.android.tv.common.SoftPreconditions; - -public final class DialogUtils { - - /** - * Shows a list in a Dialog. - * - * @param itemResIds String resource id for each item - * @param runnables Runnable for each item - */ - public static void showListDialog(Context context, int[] itemResIds, - final Runnable[] runnables) { - int size = itemResIds.length; - SoftPreconditions.checkState(size == runnables.length); - DialogInterface.OnClickListener onClickListener - = new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, int which) { - Runnable runnable = runnables[which]; - if (runnable != null) { - runnable.run(); - } - dialog.dismiss(); - } - }; - CharSequence[] items = new CharSequence[itemResIds.length]; - Resources res = context.getResources(); - for (int i = 0; i < size; ++i) { - items[i] = res.getString(itemResIds[i]); - } - new AlertDialog.Builder(context) - .setItems(items, onClickListener) - .create() - .show(); - } - - private DialogUtils() { } -} diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java index abc05bad..cf43fc9b 100644 --- a/src/com/android/tv/ui/KeypadChannelSwitchView.java +++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java @@ -41,9 +41,9 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.analytics.DurationTimer; import com.android.tv.analytics.Tracker; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; +import com.android.tv.util.SoftPreconditions; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java index 63ee199d..afea9ba5 100644 --- a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java +++ b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java @@ -35,8 +35,8 @@ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInt private static final int[] MAX_SKIPPED_VIEW_COUNT = { 1, 4 }; private static final int MSG_MOVE_FOCUS = 1000; - private final VerticalGridView mView; - private final MyHandler mHandler = new MyHandler(this); + private VerticalGridView mView; + private MyHandler mHandler = new MyHandler(this); private int mDirection; private boolean mFocusAccelerated; private long mRepeatedKeyInterval; diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java index 646f9159..032782bd 100644 --- a/src/com/android/tv/ui/SelectInputView.java +++ b/src/com/android/tv/ui/SelectInputView.java @@ -249,16 +249,14 @@ public class SelectInputView extends VerticalGridView implements boolean foundTuner = false; for (TvInputInfo input : mTvInputManagerHelper.getTvInputInfos(false, false)) { if (input.isPassthroughInput()) { - if (!input.isHidden(getContext())) { - mInputList.add(input); - inputMap.put(input.getId(), input); - } + mInputList.add(input); + inputMap.put(input.getId(), input); } else if (!foundTuner) { foundTuner = true; mInputList.add(input); } } - // Do not show HDMI ports if a CEC device is directly connected to the port. + // Do not show an AVR if an HDMI device is connected to it. for (TvInputInfo input : inputMap.values()) { if (input.getParentId() != null && !input.isConnectedToHdmiSwitch()) { mInputList.remove(inputMap.get(input.getParentId())); diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index 6d3d62aa..286bc1f9 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -17,11 +17,11 @@ package com.android.tv.ui; import android.animation.Animator; +import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.content.ContentUris; import android.content.Context; import android.content.pm.PackageManager; import android.media.PlaybackParams; @@ -29,18 +29,13 @@ import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; -import android.media.tv.TvView; import android.media.tv.TvView.OnUnhandledInputEventListener; import android.media.tv.TvView.TvInputCallback; -import android.net.ConnectivityManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.annotation.IntDef; import android.support.annotation.Nullable; -import android.support.annotation.UiThread; -import android.support.v4.os.BuildCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -51,57 +46,50 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.TextView; import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.analytics.DurationTimer; import com.android.tv.analytics.Tracker; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.recording.RecordedProgram; +import com.android.tv.common.recording.PlaybackTvView; +import com.android.tv.common.recording.RecordingUtils; import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; -import com.android.tv.dvr.DvrDataManager; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.recommendation.NotificationService; -import com.android.tv.util.NetworkUtils; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; public class TunableTvView extends FrameLayout implements StreamInfo { private static final boolean DEBUG = false; private static final String TAG = "TunableTvView"; + public static final String PERMISSION_RECEIVE_INPUT_EVENT = + "com.android.tv.permission.RECEIVE_INPUT_EVENT"; + public static final int VIDEO_UNAVAILABLE_REASON_NOT_TUNED = -1; - @Retention(RetentionPolicy.SOURCE) - @IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL}) - public @interface BlockScreenType {} public static final int BLOCK_SCREEN_TYPE_NO_UI = 0; public static final int BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW = 1; public static final int BLOCK_SCREEN_TYPE_NORMAL = 2; - private static final String PERMISSION_RECEIVE_INPUT_EVENT = - "com.android.tv.permission.RECEIVE_INPUT_EVENT"; - @Retention(RetentionPolicy.SOURCE) @IntDef({ TIME_SHIFT_STATE_NONE, TIME_SHIFT_STATE_PLAY, TIME_SHIFT_STATE_PAUSE, - TIME_SHIFT_STATE_REWIND, TIME_SHIFT_STATE_FAST_FORWARD }) - private @interface TimeShiftState {} - private static final int TIME_SHIFT_STATE_NONE = 0; - private static final int TIME_SHIFT_STATE_PLAY = 1; - private static final int TIME_SHIFT_STATE_PAUSE = 2; - private static final int TIME_SHIFT_STATE_REWIND = 3; - private static final int TIME_SHIFT_STATE_FAST_FORWARD = 4; + TIME_SHIFT_STATE_REWIND, TIME_SHIFT_STATE_FAST_FORWARD }) + public @interface TimeShiftState {} + public static final int TIME_SHIFT_STATE_NONE = 0; + public static final int TIME_SHIFT_STATE_PLAY = 1; + public static final int TIME_SHIFT_STATE_PAUSE = 2; + public static final int TIME_SHIFT_STATE_REWIND = 3; + public static final int TIME_SHIFT_STATE_FAST_FORWARD = 4; private static final int FADED_IN = 0; private static final int FADED_OUT = 1; @@ -115,7 +103,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private AppLayerTvView mTvView; private Channel mCurrentChannel; - private RecordedProgram mRecordedProgram; private TvInputManagerHelper mInputManagerHelper; private ContentRatingsManager mContentRatingsManager; @Nullable @@ -127,7 +114,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private int mVideoHeight; private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; private float mVideoFrameRate; - private float mVideoDisplayAspectRatio; private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; private boolean mHasClosedCaption = false; private boolean mVideoAvailable; @@ -144,7 +130,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private boolean mIsPip; private int mScreenHeight; private int mShrunkenTvViewHeight; - private final boolean mCanModifyParentalControls; + private boolean mCanModifyParentalControls; @TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE; private TimeShiftListener mTimeShiftListener; @@ -153,14 +139,22 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private final Tracker mTracker; private final DurationTimer mChannelViewTimer = new DurationTimer(); - private InternetCheckTask mInternetCheckTask; // A block screen view which has lock icon with black background. // This indicates that user's action is needed to play video. - private final BlockScreenView mBlockScreenView; + private final View mBlockScreenView; + + private final View mBlockScreenDescriptionView; + private final ImageView mBlockScreenIconView; + private final View mBlockScreenShrunkenIconView; + private final TextView mBlockScreenTextView; + + // Animators used for fade in/out of block screen icon. + private final Animator mBlockScreenDescriptionFadeIn; + private final Animator mBlockScreenDescriptionFadeOut; // A View to hide screen when there's problem in video playback. - private final BlockScreenView mHideScreenView; + private final TextView mHideScreenView; // A View to block screen until onContentAllowed is received if parental control is on. private final View mBlockScreenForTuneView; @@ -173,11 +167,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private int mFadeState = FADED_IN; private Runnable mActionAfterFade; - @BlockScreenType private int mBlockScreenType; - - private final DvrDataManager mDvrDataManager; - private final ChannelDataManager mChannelDataManager; - private final ConnectivityManager mConnectivityManager; + private int mBlockScreenType; private final TvInputCallback mCallback = new TvInputCallback() { @@ -249,7 +239,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mVideoHeight = 0; mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; mVideoFrameRate = 0f; - mVideoDisplayAspectRatio = 0f; } else if (type == TvTrackInfo.TYPE_AUDIO) { mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; } @@ -265,18 +254,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mVideoFormat = Utils.getVideoDefinitionLevelFromSize( mVideoWidth, mVideoHeight); mVideoFrameRate = track.getVideoFrameRate(); - if (mVideoWidth <= 0 || mVideoHeight <= 0) { - mVideoDisplayAspectRatio = 0.0f; - } else if (android.os.Build.VERSION.SDK_INT >= - android.os.Build.VERSION_CODES.M) { - float VideoPixelAspectRatio = - track.getVideoPixelAspectRatio(); - mVideoDisplayAspectRatio = VideoPixelAspectRatio - * mVideoWidth / mVideoHeight; - } else { - mVideoDisplayAspectRatio = mVideoWidth - / (float) mVideoHeight; - } } else if (type == TvTrackInfo.TYPE_AUDIO) { mAudioChannelCount = track.getAudioChannelCount(); } @@ -338,8 +315,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override @TargetApi(Build.VERSION_CODES.M) public void onTimeShiftStatusChanged(String inputId, int status) { - boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE; - setTimeShiftAvailable(available); + setTimeShiftAvailable(status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); } }; @@ -360,32 +336,53 @@ public class TunableTvView extends FrameLayout implements StreamInfo { inflate(getContext(), R.layout.tunable_tv_view, this); ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDvrDataManager = CommonFeatures.DVR.isEnabled(context) && BuildCompat.isAtLeastN() - ? appSingletons.getDvrDataManager() - : null; - mChannelDataManager = appSingletons.getChannelDataManager(); - mConnectivityManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context); mTracker = appSingletons.getTracker(); mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL; - mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen); + mBlockScreenView = findViewById(R.id.block_screen); + mBlockScreenDescriptionView = findViewById(R.id.block_screen_description); + + mBlockScreenIconView = (ImageView) mBlockScreenView.findViewById(R.id.block_screen_icon); if (!mCanModifyParentalControls) { - mBlockScreenView.setImage(R.drawable.ic_message_lock_no_permission); - mBlockScreenView.setScaleType(ImageView.ScaleType.CENTER); - } else { - mBlockScreenView.setImage(R.drawable.ic_message_lock); + mBlockScreenIconView.setImageResource(R.drawable.ic_message_lock_no_permission); + mBlockScreenIconView.setScaleType(ImageView.ScaleType.CENTER); } - mBlockScreenView.setShrunkenImage(R.drawable.ic_message_lock_preview); - mBlockScreenView.addFadeOutAnimationListener(new AnimatorListenerAdapter() { + mBlockScreenShrunkenIconView = mBlockScreenView.findViewById( + R.id.block_screen_shrunken_icon); + mBlockScreenTextView = (TextView) mBlockScreenView.findViewById(R.id.block_screen_text); + + mBlockScreenDescriptionFadeIn = AnimatorInflater.loadAnimator(context, + R.animator.tvview_block_screen_fade_in); + mBlockScreenDescriptionFadeIn.setTarget(mBlockScreenDescriptionView); + mBlockScreenDescriptionFadeIn.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + switch (mBlockScreenType) { + case BLOCK_SCREEN_TYPE_NORMAL: + mBlockScreenIconView.setVisibility(VISIBLE); + mBlockScreenShrunkenIconView.setVisibility(GONE); + break; + case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: + mBlockScreenIconView.setVisibility(GONE); + mBlockScreenShrunkenIconView.setVisibility(VISIBLE); + break; + } + mBlockScreenDescriptionView.setVisibility(VISIBLE); + } + }); + mBlockScreenDescriptionFadeOut = AnimatorInflater.loadAnimator(context, + R.animator.tvview_block_screen_fade_out); + mBlockScreenDescriptionFadeOut.setTarget(mBlockScreenDescriptionView); + mBlockScreenDescriptionFadeOut.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - adjustBlockScreenSpacingAndText(); + mBlockScreenDescriptionView.setVisibility(GONE); + mBlockScreenDescriptionView.setAlpha(1f); + updateBlockScreenTextView(); } }); - mHideScreenView = (BlockScreenView) findViewById(R.id.hide_screen); - mHideScreenView.setImageVisibility(false); + mHideScreenView = (TextView) findViewById(R.id.hide_screen); mBufferingSpinnerView = findViewById(R.id.buffering_spinner); mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune); mDimScreenView = findViewById(R.id.dim); @@ -444,7 +441,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { public void reset() { mTvView.reset(); mCurrentChannel = null; - mRecordedProgram = null; mInputInfo = null; mCanReceiveInputEvent = false; mOnTuneListener = null; @@ -475,82 +471,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Returns {@code true}, if this view is the recording playback mode. - */ - public boolean isRecordingPlayback() { - return mRecordedProgram != null; - } - - /** - * Returns the recording which is being played right now. - */ - public RecordedProgram getPlayingRecordedProgram() { - return mRecordedProgram; - } - - /** * Plays a recording. */ - public boolean playRecording(Uri recordingUri, OnTuneListener listener) { - if (!mStarted) { - throw new IllegalStateException("TvView isn't started"); - } - if (!CommonFeatures.DVR.isEnabled(getContext()) || !BuildCompat.isAtLeastN()) { - return false; - } - if (DEBUG) Log.d(TAG, "playRecording " + recordingUri); - long recordingId = ContentUris.parseId(recordingUri); - mRecordedProgram = mDvrDataManager.getRecordedProgram(recordingId); - if (mRecordedProgram == null) { - Log.w(TAG, "No recorded program (Uri=" + recordingUri + ")"); - return false; - } - String inputId = mRecordedProgram.getInputId(); - TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId); - if (inputInfo == null) { - return false; - } - mOnTuneListener = listener; - // mCurrentChannel can be null. - mCurrentChannel = mChannelDataManager.getChannel(mRecordedProgram.getChannelId()); - // For recording playback, input event should not be sent. - mCanReceiveInputEvent = false; - boolean needSurfaceSizeUpdate = false; - if (!inputInfo.equals(mInputInfo)) { - mInputInfo = inputInfo; - if (DEBUG) { - Log.d(TAG, "Input \'" + mInputInfo.getId() + "\' can receive input event: " - + mCanReceiveInputEvent); - } - needSurfaceSizeUpdate = true; - } - mChannelViewTimer.start(); - mVideoWidth = 0; - mVideoHeight = 0; - mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; - mVideoFrameRate = 0f; - mVideoDisplayAspectRatio = 0f; - mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; - mHasClosedCaption = false; - mTvView.setCallback(mCallback); - mTimeShiftCurrentPositionMs = INVALID_TIME; - mTvView.setTimeShiftPositionCallback(null); - setTimeShiftAvailable(false); - mTvView.timeShiftPlay(inputId, recordingUri); - if (needSurfaceSizeUpdate && mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) { - // When the input is changed, TvView recreates its SurfaceView internally. - // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView. - getSurfaceView().getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight); - } - hideScreenByVideoAvailability(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); - unblockScreenByContentRating(); - if (mParentControlEnabled) { - mBlockScreenForTuneView.setVisibility(View.VISIBLE); - } - if (mOnTuneListener != null) { - mOnTuneListener.onStreamInfoChanged(this); - } - return true; + public boolean playRecording(String inputId, Uri recordingUri, OnTuneListener listener) { + // Create a dummy channel. + Channel channel = new Channel.Builder() + .setId(0) + .setInputId(inputId) + .build(); + return tuneTo(channel, RecordingUtils.buildMediaUri(recordingUri), listener); } /** @@ -579,7 +508,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } mOnTuneListener = listener; mCurrentChannel = channel; - mRecordedProgram = null; boolean tunedByRecommendation = params != null && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null; boolean needSurfaceSizeUpdate = false; @@ -600,7 +528,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mVideoHeight = 0; mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; mVideoFrameRate = 0f; - mVideoDisplayAspectRatio = 0f; mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; mHasClosedCaption = false; mTvView.setCallback(mCallback); @@ -663,7 +590,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called, * {@link android.view.SurfaceView} and its underlying window can be misaligned, when the size * of {@link android.view.SurfaceView} is changed without changing either left position or top - * position. For detail, please refer the codes of android.view.SurfaceView.updateWindow(). + * position. For detail, please refer the codes of {@link android.view.SurfaceView#updateWindow} + * . */ public void setFixedSurfaceSize(int width, int height) { mFixedSurfaceWidth = width; @@ -708,18 +636,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { void onContentAllowed(); } - public void unblockContent(TvContentRating rating) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - try { - Method method = TvView.class.getMethod("requestUnblockContent", - TvContentRating.class); - method.invoke(mTvView, rating); - } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) { - e.printStackTrace(); - } - } else { - mTvView.unblockContent(rating); - } + public void requestUnblockContent(TvContentRating rating) { + mTvView.unblockContent(rating); } @Override @@ -742,14 +660,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return mVideoFrameRate; } - /** - * Returns displayed aspect ratio (video width / video height * pixel ratio). - */ - @Override - public float getVideoDisplayAspectRatio() { - return mVideoDisplayAspectRatio; - } - @Override public int getAudioChannelCount() { return mAudioChannelCount; @@ -773,7 +683,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { /** * Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}. */ - private SurfaceView getSurfaceView() { + public SurfaceView getSurfaceView() { return (SurfaceView) mTvView.getChildAt(0); } @@ -847,7 +757,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mScreenHeight; } // TODO: need to get UX confirmation. - mBlockScreenView.scaleContainerView(scale); + mBlockScreenDescriptionView.setScaleX(scale); + mBlockScreenDescriptionView.setScaleY(scale); } } @@ -906,7 +817,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * * @param type The type of block screen to set. */ - public void setBlockScreenType(@BlockScreenType int type) { + public void setBlockScreenType(int type) { // TODO: need to support the transition from NORMAL to SHRUNKEN and vice verse. if (mBlockScreenType != type) { mBlockScreenType = type; @@ -915,7 +826,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } private void updateBlockScreenUI(boolean animation) { - mBlockScreenView.endAnimations(); + if (mBlockScreenDescriptionFadeIn.isRunning()) { + mBlockScreenDescriptionFadeIn.end(); + } + if (mBlockScreenDescriptionFadeOut.isRunning()) { + mBlockScreenDescriptionFadeOut.end(); + } if (!mScreenBlocked && mBlockedContentRating == null) { mBlockScreenView.setVisibility(GONE); @@ -923,72 +839,101 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } mBlockScreenView.setVisibility(VISIBLE); - if (!animation || mBlockScreenType != TunableTvView.BLOCK_SCREEN_TYPE_NO_UI) { - adjustBlockScreenSpacingAndText(); + if (!animation) { + updateBlockScreenTextView(); + switch (mBlockScreenType) { + case BLOCK_SCREEN_TYPE_NO_UI: + mBlockScreenIconView.setVisibility(GONE); + mBlockScreenShrunkenIconView.setVisibility(GONE); + mBlockScreenDescriptionView.setVisibility(GONE); + break; + case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: + mBlockScreenIconView.setVisibility(GONE); + mBlockScreenShrunkenIconView.setVisibility(VISIBLE); + mBlockScreenDescriptionView.setVisibility(VISIBLE); + break; + case BLOCK_SCREEN_TYPE_NORMAL: + mBlockScreenIconView.setVisibility(VISIBLE); + mBlockScreenShrunkenIconView.setVisibility(GONE); + mBlockScreenDescriptionView.setVisibility(VISIBLE); + break; + } + } else { + switch (mBlockScreenType) { + case BLOCK_SCREEN_TYPE_NO_UI: + if (mBlockScreenDescriptionView.getVisibility() == VISIBLE) { + mBlockScreenDescriptionFadeOut.start(); + } + break; + case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: + case BLOCK_SCREEN_TYPE_NORMAL: + updateBlockScreenTextView(); + if (mBlockScreenDescriptionView.getVisibility() == GONE) { + mBlockScreenDescriptionFadeIn.start(); + } + break; + } } - mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation); } - private void adjustBlockScreenSpacingAndText() { + private void updateBlockScreenTextView() { // TODO: need to add animation for padding change when the block screen type is changed // NORMAL to SHRUNKEN and vice verse. - mBlockScreenView.setSpacing(mBlockScreenType); - String text = getBlockScreenText(); - if (text != null) { - mBlockScreenView.setText(text); - } - } + mBlockScreenTextView.setPadding(0, + getResources().getDimensionPixelOffset( + mBlockScreenType == BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW + ? R.dimen.shrunken_tvview_block_text_padding_top + : R.dimen.tvview_block_text_padding_top), + 0, 0); - /** - * Returns the block screen text corresponding to the current status. - * Note that returning {@code null} value means that the current text should not be changed. - */ - private String getBlockScreenText() { if (mScreenBlocked) { switch (mBlockScreenType) { case BLOCK_SCREEN_TYPE_NO_UI: case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: - return ""; + mBlockScreenTextView.setText(""); + break; case BLOCK_SCREEN_TYPE_NORMAL: if (mCanModifyParentalControls) { - return getResources().getString(R.string.tvview_channel_locked); + mBlockScreenTextView.setText(R.string.tvview_channel_locked); } else { - return getResources().getString( - R.string.tvview_channel_locked_no_permission); + mBlockScreenTextView.setText(R.string.tvview_channel_locked_no_permission); } + break; } } else if (mBlockedContentRating != null) { String name = mContentRatingsManager.getDisplayNameForRating(mBlockedContentRating); switch (mBlockScreenType) { case BLOCK_SCREEN_TYPE_NO_UI: - return ""; + mBlockScreenTextView.setText(""); + break; case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: if (TextUtils.isEmpty(name)) { - return getResources().getString(R.string.shrunken_tvview_content_locked); + mBlockScreenTextView.setText(R.string.shrunken_tvview_content_locked); } else { - return getContext().getString( - R.string.shrunken_tvview_content_locked_format, name); + mBlockScreenTextView.setText(getContext().getString( + R.string.shrunken_tvview_content_locked_format, name)); } + break; case BLOCK_SCREEN_TYPE_NORMAL: if (TextUtils.isEmpty(name)) { if (mCanModifyParentalControls) { - return getResources().getString(R.string.tvview_content_locked); + mBlockScreenTextView.setText(R.string.tvview_content_locked); } else { - return getResources().getString( + mBlockScreenTextView.setText( R.string.tvview_content_locked_no_permission); } } else { if (mCanModifyParentalControls) { - return getContext().getString( - R.string.tvview_content_locked_format, name); + mBlockScreenTextView.setText(getContext().getString( + R.string.tvview_content_locked_format, name)); } else { - return getContext().getString( - R.string.tvview_content_locked_format_no_permission, name); + mBlockScreenTextView.setText(getContext().getString( + R.string.tvview_content_locked_format_no_permission, name)); } } + break; } } - return null; } private void checkBlockScreenAndMuteNeeded() { @@ -1023,18 +968,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo { checkBlockScreenAndMuteNeeded(); } - @UiThread private void hideScreenByVideoAvailability(int reason) { - mVideoAvailable = false; - mVideoUnavailableReason = reason; - if (mInternetCheckTask != null) { - mInternetCheckTask.cancel(true); - mInternetCheckTask = null; - } switch (reason) { case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); mHideScreenView.setText(R.string.tvview_msg_audio_only); mBufferingSpinnerView.setVisibility(GONE); unmuteIfPossible(); @@ -1043,33 +980,19 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mBufferingSpinnerView.setVisibility(VISIBLE); mute(); break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setText(R.string.tvview_msg_weak_signal); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - break; + case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: + case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: case VIDEO_UNAVAILABLE_REASON_NOT_TUNED: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(null); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: default: mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); mHideScreenView.setText(null); mBufferingSpinnerView.setVisibility(GONE); mute(); - if (mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) { - mInternetCheckTask = new InternetCheckTask(); - mInternetCheckTask.execute(); - } break; } + mVideoAvailable = false; + mVideoUnavailableReason = reason; } private void unhideScreenByVideoAvailability() { @@ -1171,7 +1094,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } mTimeShiftAvailable = isTimeShiftAvailable; if (isTimeShiftAvailable) { - mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() { + mTvView.setTimeShiftPositionCallback(new PlaybackTvView.TimeShiftPositionCallback2() { @Override public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { if (mTimeShiftListener != null && mCurrentChannel != null @@ -1184,6 +1107,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo { public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { mTimeShiftCurrentPositionMs = timeMs; } + + @Override + public void onTimeShiftEndPositionChanged(String inputId, long timeMs) { + if (mTimeShiftListener != null && mCurrentChannel != null + && mCurrentChannel.getInputId().equals(inputId)) { + mTimeShiftListener.onRecordEndTimeChanged(timeMs); + } + } }); } else { mTvView.setTimeShiftPositionCallback(null); @@ -1334,6 +1265,11 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * Called when the record start time has been changed. */ public abstract void onRecordStartTimeChanged(long recordStartTimeMs); + + /** + * Called when the record end time has been changed. + */ + public abstract void onRecordEndTimeChanged(long recordEndTimeMs); } /** @@ -1345,22 +1281,4 @@ public class TunableTvView extends FrameLayout implements StreamInfo { */ public abstract void onScreenBlockingChanged(boolean blocked); } - - public class InternetCheckTask extends AsyncTask<Void, Void, Boolean> { - @Override - protected Boolean doInBackground(Void... params) { - return NetworkUtils.isNetworkAvailable(mConnectivityManager); - } - - @Override - protected void onPostExecute(Boolean networkAvailable) { - mInternetCheckTask = null; - if (!mVideoAvailable && !networkAvailable && isAttachedToWindow() - && mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) { - mHideScreenView.setImageVisibility(true); - mHideScreenView.setImage(R.drawable.ic_sad_cloud); - mHideScreenView.setText(R.string.tvview_msg_no_internet_connection); - } - } - } } diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java index 94f9b0f9..124f3393 100644 --- a/src/com/android/tv/ui/TvOverlayManager.java +++ b/src/com/android/tv/ui/TvOverlayManager.java @@ -29,7 +29,6 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.support.v4.os.BuildCompat; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; @@ -47,7 +46,6 @@ import com.android.tv.TimeShiftManager; import com.android.tv.TvApplication; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; -import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.ui.setup.OnActionClickListener; import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; @@ -56,9 +54,7 @@ import com.android.tv.dialog.FullscreenDialogFragment; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dialog.RecentlyWatchedDialogFragment; import com.android.tv.dialog.SafeDismissDialogFragment; -import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.ui.DvrActivity; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; import com.android.tv.guide.ProgramGuide; import com.android.tv.menu.Menu; import com.android.tv.menu.Menu.MenuShowReason; @@ -137,7 +133,6 @@ public class TvOverlayManager { AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG); - AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG); } private final MainActivity mMainActivity; @@ -160,7 +155,7 @@ public class TvOverlayManager { private @TvOverlayType int mOpenedOverlays; - private final List<Runnable> mPendingActions = new ArrayList<>(); + private List<Runnable> mPendingActions = new ArrayList<>(); public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, KeypadChannelSwitchView keypadChannelSwitchView, @@ -232,13 +227,9 @@ public class TvOverlayManager { onOverlayClosed(OVERLAY_TYPE_GUIDE); } }; - DvrDataManager dvrDataManager = - CommonFeatures.DVR.isEnabled(mainActivity) && BuildCompat.isAtLeastN() ? singletons - .getDvrDataManager() : null; mProgramGuide = new ProgramGuide(mainActivity, channelTuner, singletons.getTvInputManagerHelper(), mChannelDataManager, - singletons.getProgramDataManager(), dvrDataManager, singletons.getTracker(), - preShowRunnable, + singletons.getProgramDataManager(), singletons.getTracker(), preShowRunnable, postHideRunnable); mSetupFragment = new SetupSourcesFragment(); mSetupFragment.setOnActionClickListener(new OnActionClickListener() { @@ -355,18 +346,10 @@ public class TvOverlayManager { */ public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory) { - showDialogFragment(tag, dialog, keepSidePanelHistory, false); - } - - public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, - boolean keepSidePanelHistory, boolean keepProgramGuide) { int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG; if (keepSidePanelHistory) { flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY; } - if (keepProgramGuide) { - flags |= FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE; - } hideOverlays(flags); // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV. if (!AVAILABLE_DIALOG_TAGS.contains(tag)) { @@ -439,6 +422,7 @@ public class TvOverlayManager { public void showSetupFragment() { if (DEBUG) Log.d(TAG, "showSetupFragment"); mSetupFragmentActive = true; + SetupSourcesFragment.setTheme(R.style.Theme_TV_GuidedStep); mSetupFragment.enableFragmentTransition(SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION | SetupFragment.FRAGMENT_REENTER_TRANSITION); @@ -453,6 +437,7 @@ public class TvOverlayManager { return; } mSetupFragmentActive = false; + SetupSourcesFragment.setTheme(SetupSourcesFragment.DEFAULT_THEME); closeFragment(removeFragment ? mSetupFragment : null); if (mChannelDataManager.getChannelCount() == 0) { mMainActivity.finish(); diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java index 52e96cc0..444b5c0c 100644 --- a/src/com/android/tv/ui/TvTransitionManager.java +++ b/src/com/android/tv/ui/TvTransitionManager.java @@ -33,7 +33,6 @@ import android.widget.FrameLayout.LayoutParams; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -90,21 +89,15 @@ public class TvTransitionManager extends TransitionManager { } initIfNeeded(); if (withAnimation) { - mEmptyView.setAlpha(1.0f); transitionTo(mEmptyScene); } else { TransitionManager.go(mEmptyScene, null); - // When transition is null, transition got stuck without calling endTransitions. - TransitionManager.endTransitions(mEmptyScene.getSceneRoot()); - // Since Fade.OUT transition doesn't run, we need to set alpha manually. - mEmptyView.setAlpha(0); } } public void goToChannelBannerScene() { initIfNeeded(); - Channel channel = mMainActivity.getCurrentChannel(); - if (channel != null && channel.isPassthrough()) { + if (mMainActivity.getCurrentChannel().isPassthrough()) { if (mCurrentScene != mInputBannerScene) { // Show the input banner instead. LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams(); @@ -159,7 +152,7 @@ public class TvTransitionManager extends TransitionManager { mExitAnimator = AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit); - mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView); + mEmptyScene = new Scene(mSceneContainer, mEmptyView); mEmptyScene.setEnterAction(new Runnable() { @Override public void run() { diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java index 5ad89bfa..d767906b 100644 --- a/src/com/android/tv/ui/TvViewUiManager.java +++ b/src/com/android/tv/ui/TvViewUiManager.java @@ -59,7 +59,6 @@ public class TvViewUiManager { private static final boolean DEBUG = false; private static final float DISPLAY_MODE_EPSILON = 0.001f; - private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f; private final Context mContext; private final Resources mResources; @@ -72,8 +71,8 @@ public class TvViewUiManager { private final int mTvViewShrunkenEndMargin; private final int mTvViewPapStartMargin; private final int mTvViewPapEndMargin; - private int mWindowWidth; - private int mWindowHeight; + private final int mScreenWidth; + private final int mScreenHeight; private final int mPipViewHorizontalMargin; private final int mPipViewTopMargin; private final int mPipViewBottomMargin; @@ -102,28 +101,28 @@ public class TvViewUiManager { private ObjectAnimator mBackgroundAnimator; private int mBackgroundColor; private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED; + private int mAppliedVideoWidth; + private int mAppliedVideoHeight; private int mAppliedTvViewStartMargin; private int mAppliedTvViewEndMargin; - private float mAppliedVideoDisplayAspectRatio; public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView, FrameLayout contentView, TvOptionsManager tvOptionManager) { mContext = context; - mResources = mContext.getResources(); + mResources = context.getResources(); mTvView = tvView; mPipView = pipView; mContentView = contentView; mTvOptionsManager = tvOptionManager; - DisplayManager displayManager = (DisplayManager) mContext + DisplayManager displayManager = (DisplayManager) context .getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); Point size = new Point(); display.getSize(size); - mWindowWidth = size.x; - mWindowHeight = size.y; + mScreenWidth = size.x; + mScreenHeight = size.y; - // Have an assumption that PIP and TvView Shrinking happens only in full screen. mTvViewShrunkenStartMargin = mResources .getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start); mTvViewShrunkenEndMargin = @@ -132,7 +131,7 @@ public class TvViewUiManager { int papMarginHorizontal = mResources .getDimensionPixelOffset(R.dimen.papview_margin_horizontal); int papSpacing = mResources.getDimensionPixelOffset(R.dimen.papview_spacing); - mTvViewPapWidth = (mWindowWidth - papSpacing) / 2 - papMarginHorizontal; + mTvViewPapWidth = (mScreenWidth - papSpacing) / 2 - papMarginHorizontal; mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing; mTvViewPapEndMargin = papMarginHorizontal; mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0); @@ -148,21 +147,6 @@ public class TvViewUiManager { .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal); mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top); mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom); - mContentView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - int windowWidth = right - left; - int windowHeight = bottom - top; - if (windowWidth > 0 && windowHeight > 0) { - if (mWindowWidth != windowWidth || mWindowHeight != windowHeight) { - mWindowWidth = windowWidth; - mWindowHeight = windowHeight; - applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, true); - } - } - } - }); } /** @@ -186,7 +170,7 @@ public class TvViewUiManager { mTvViewEndMarginBeforeShrunken = mTvViewEndMargin; if (mPipStarted && getPipLayout() == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { float sidePanelWidth = mResources.getDimensionPixelOffset(R.dimen.side_panel_width); - float factor = 1.0f - sidePanelWidth / mWindowWidth; + float factor = 1.0f - sidePanelWidth / mScreenWidth; int startMargin = (int) (mTvViewPapStartMargin * factor); int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth); setTvViewMargin(startMargin, endMargin); @@ -225,21 +209,25 @@ public class TvViewUiManager { int viewWidth = mContentView.getWidth(); int viewHeight = mContentView.getHeight(); - float videoDisplayAspectRatio = mTvView.getVideoDisplayAspectRatio(); - if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) { + int videoWidth = mTvView.getVideoWidth(); + int videoHeight = mTvView.getVideoHeight(); + + if (viewWidth <= 0 || viewHeight <= 0 || videoWidth <= 0 || videoHeight <= 0) { Log.w(TAG, "Video size is currently unavailable"); if (DEBUG) { Log.d(TAG, "isDisplayModeAvailable: " + "viewWidth=" + viewWidth + ", viewHeight=" + viewHeight - + ", videoDisplayAspectRatio=" + videoDisplayAspectRatio + + ", videoWidth=" + videoWidth + + ", videoHeight="+ videoHeight ); } return false; } float viewRatio = viewWidth / (float) viewHeight; - return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON; + float videoRatio = videoWidth / (float) videoHeight; + return Math.abs(viewRatio - videoRatio) >= DISPLAY_MODE_EPSILON; } /** @@ -263,7 +251,7 @@ public class TvViewUiManager { if (storeInPreference) { mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply(); } - applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), animate, false); + applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight(), animate); return prev; } @@ -280,7 +268,7 @@ public class TvViewUiManager { * Updates TvView. It is called when video resolution is updated. */ public void updateTvView() { - applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false); + applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight(), false); if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) { mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration), mFastOutLinearIn, null); @@ -519,11 +507,11 @@ public class TvViewUiManager { @Override public void run() { if (DEBUG) { - Log.d(TAG, "setFixedSize: w=" + layoutParams.width + " h=" - + layoutParams.height); + Log.d(TAG, "setFixedSize: w=" + mTvView.getWidth() + " h=" + mTvView + .getHeight()); } mTvView.setLayoutParams(layoutParams); - mTvView.setFixedSurfaceSize(layoutParams.width, layoutParams.height); + mTvView.setFixedSurfaceSize(mTvView.getWidth(), mTvView.getHeight()); } }); } else { @@ -553,14 +541,15 @@ public class TvViewUiManager { if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { gravity = Gravity.CENTER_VERTICAL | Gravity.START; height = tvViewFrame.height; - float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio(); - if (videoDisplayAspectRatio <= 0f) { - width = tvViewFrame.width; - } else { - width = (int) (height * videoDisplayAspectRatio); + int pipVideoWidth = mPipView.getVideoWidth(); + int pipVideoHeight = mPipView.getVideoHeight(); + if (pipVideoWidth > 0 && pipVideoHeight > 0) { + width = height * pipVideoWidth / pipVideoHeight; if (width > tvViewFrame.width) { width = tvViewFrame.width; } + } else { + width = tvViewFrame.width; } startMargin = mResources.getDimensionPixelOffset(R.dimen.papview_margin_horizontal) * tvViewFrame.width / mTvViewPapWidth + (tvViewFrame.width - width) / 2; @@ -574,8 +563,8 @@ public class TvViewUiManager { int tvEndMargin = tvViewFrame.getMarginEnd(); int tvTopMargin = tvViewFrame.topMargin; int tvBottomMargin = tvViewFrame.bottomMargin; - float horizontalScaleFactor = (float) tvViewWidth / mWindowWidth; - float verticalScaleFactor = (float) tvViewHeight / mWindowHeight; + float horizontalScaleFactor = (float) tvViewWidth / mScreenWidth; + float verticalScaleFactor = (float) tvViewHeight / mScreenHeight; int maxWidth; if (mPipSize == TvSettings.PIP_SIZE_SMALL) { @@ -591,14 +580,15 @@ public class TvViewUiManager { } else { throw new IllegalArgumentException("Invalid PIP size: " + mPipSize); } - float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio(); - if (videoDisplayAspectRatio <= 0f) { - width = maxWidth; - } else { - width = (int) (height * videoDisplayAspectRatio); + int pipVideoWidth = mPipView.getVideoWidth(); + int pipVideoHeight = mPipView.getVideoHeight(); + if (pipVideoWidth > 0 && pipVideoHeight > 0) { + width = height * pipVideoWidth / pipVideoHeight; if (width > maxWidth) { width = maxWidth; } + } else { + width = maxWidth; } startMargin = tvStartMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor); @@ -713,29 +703,31 @@ public class TvViewUiManager { }); } - private void applyDisplayMode(float videoDisplayAspectRatio, boolean animate, - boolean forceUpdate) { + private void applyDisplayMode(int videoWidth, int videoHeight, boolean animate) { if (mAppliedDisplayedMode == mDisplayMode + && mAppliedVideoWidth == videoWidth + && mAppliedVideoHeight == videoHeight && mAppliedTvViewStartMargin == mTvViewStartMargin - && mAppliedTvViewEndMargin == mTvViewEndMargin - && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) < - DISPLAY_ASPECT_RATIO_EPSILON) { - if (!forceUpdate) { - return; - } + && mAppliedTvViewEndMargin == mTvViewEndMargin) { + return; } else { mAppliedDisplayedMode = mDisplayMode; + mAppliedVideoHeight = videoHeight; + mAppliedVideoWidth = videoWidth; mAppliedTvViewStartMargin = mTvViewStartMargin; mAppliedTvViewEndMargin = mTvViewEndMargin; - mAppliedVideoDisplayAspectRatio = videoDisplayAspectRatio; } - int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin; - int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth; + int availableAreaWidth = mScreenWidth - mTvViewStartMargin - mTvViewEndMargin; + int availableAreaHeight = availableAreaWidth * mScreenHeight / mScreenWidth; FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0, ((FrameLayout.LayoutParams) mTvView.getLayoutParams()).gravity); int displayMode = mDisplayMode; double availableAreaRatio = 0; double videoRatio = 0; + if (videoWidth <= 0 || videoHeight <= 0) { + videoWidth = mScreenWidth; + videoHeight = mScreenHeight; + } if (availableAreaWidth <= 0 || availableAreaHeight <= 0) { displayMode = DisplayMode.MODE_FULL; Log.w(TAG, "Some resolution info is missing during applyDisplayMode. (" @@ -743,14 +735,10 @@ public class TvViewUiManager { + availableAreaHeight + ")"); } else { availableAreaRatio = (double) availableAreaWidth / availableAreaHeight; - if (videoDisplayAspectRatio <= 0f) { - videoRatio = (double) mWindowWidth / mWindowHeight; - } else { - videoRatio = videoDisplayAspectRatio; - } + videoRatio = (double) videoWidth / videoHeight; } - int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2; + int tvViewFrameTop = (mScreenHeight - availableAreaHeight) / 2; MarginLayoutParams tvViewFrame = createMarginLayoutParams( mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); layoutParams.width = availableAreaWidth; @@ -789,7 +777,7 @@ public class TvViewUiManager { int marginStart = mTvViewStartMargin + (availableAreaWidth - layoutParams.width) / 2; layoutParams.setMarginStart(marginStart); // Set marginEnd as well because setTvViewPosition uses both start/end margin. - layoutParams.setMarginEnd(mWindowWidth - layoutParams.width - marginStart); + layoutParams.setMarginEnd(mScreenWidth - layoutParams.width - marginStart); setBackgroundColor(Utils.getColor(mResources, isTvViewFullScreen() ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview), @@ -822,8 +810,8 @@ public class TvViewUiManager { lp.setMarginEnd(endMargin); lp.topMargin = topMargin; lp.bottomMargin = bottomMargin; - lp.width = mWindowWidth - startMargin - endMargin; - lp.height = mWindowHeight - topMargin - bottomMargin; + lp.width = mScreenWidth - startMargin - endMargin; + lp.height = mScreenHeight - topMargin - bottomMargin; return lp; } } diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java index b52302b6..85050dc4 100644 --- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java +++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java @@ -37,7 +37,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Iterator; public class CustomizeChannelListFragment extends SideFragment { private static final int GROUP_BY_SOURCE = 0; @@ -158,21 +157,6 @@ public class CustomizeChannelListFragment extends SideFragment { return mItems; } - private void cleanUpOneChannelGroupItem(List<Item> items) { - Iterator<Item> iter = items.iterator(); - while (iter.hasNext()) { - Item item = iter.next(); - if (item instanceof SelectGroupItem) { - SelectGroupItem selectGroupItem = (SelectGroupItem) item; - if (selectGroupItem.mChannelItemsInGroup.size() == 1) { - ((ChannelItem) selectGroupItem.mChannelItemsInGroup.get(0)) - .mSelectGroupItem = null; - iter.remove(); - } - } - } - } - private void addItemForGroupBySource(List<Item> items) { items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_sources))); SelectGroupItem selectGroupItem = null; @@ -193,7 +177,6 @@ public class CustomizeChannelListFragment extends SideFragment { items.add(channelItem); selectGroupItem.addChannelItem(channelItem); } - cleanUpOneChannelGroupItem(items); } private void addItemForGroupByHdSd(List<Item> items) { @@ -228,7 +211,6 @@ public class CustomizeChannelListFragment extends SideFragment { items.add(channelItem); selectGroupItem.addChannelItem(channelItem); } - cleanUpOneChannelGroupItem(items); } private static boolean isHdChannel(Channel channel) { @@ -293,7 +275,7 @@ public class CustomizeChannelListFragment extends SideFragment { } private class ChannelItem extends ChannelCheckItem { - private SelectGroupItem mSelectGroupItem; + private final SelectGroupItem mSelectGroupItem; public ChannelItem(Channel channel, SelectGroupItem selectGroupItem) { super(channel, getChannelDataManager(), getProgramDataManager()); @@ -310,9 +292,7 @@ public class CustomizeChannelListFragment extends SideFragment { protected void onSelected() { super.onSelected(); getChannelDataManager().updateBrowsable(getChannel().getId(), isChecked()); - if (mSelectGroupItem != null) { - mSelectGroupItem.notifyUpdated(); - } + mSelectGroupItem.notifyUpdated(); } @Override diff --git a/src/com/android/tv/ui/sidepanel/DeveloperFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperFragment.java new file mode 100644 index 00000000..44b4d452 --- /dev/null +++ b/src/com/android/tv/ui/sidepanel/DeveloperFragment.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 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.android.tv.ui.sidepanel; + +import android.app.Activity; +import android.content.Context; +import android.view.View; + +import com.android.tv.Features; +import com.android.tv.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows developer options like enabling USB TV tuner. + */ +public class DeveloperFragment extends SideFragment { + private static final String TRACKER_LABEL = "developer options"; + + /** + * Sets USB TV tuner enabled. + */ + private static final class UsbTvTunerItem extends SwitchItem { + Context mContext; + + public UsbTvTunerItem(Context context) { + super(context.getResources().getString(R.string.developer_menu_enable_usb_tv_tuner), + context.getResources().getString(R.string.developer_menu_enable_usb_tv_tuner), + context.getResources().getString( + R.string.developer_menu_enable_usb_tv_tuner_description)); + mContext = context; + } + + @Override + protected void onBind(View view) { + super.onBind(view); + setChecked(Features.USB_TUNER.isEnabled(view.getContext())); + } + + @Override + public void setChecked(boolean checked) { + super.setChecked(checked); + Features.USB_TUNER.setEnabled(mContext, checked); + } + } + + @Override + protected String getTitle() { + return getResources().getString(R.string.side_panel_title_developer); + } + + @Override + public String getTrackerLabel() { + return TRACKER_LABEL; + } + + @Override + protected List<Item> getItemList() { + List<Item> items = new ArrayList<>(); + Activity activity = getActivity(); + items.add(new UsbTvTunerItem(activity)); + boolean ac3Support = getMainActivity().isAc3PassthroughSupported(); + // Show AC3 passthrough availability. + items.add(new SimpleItem(getString(R.string.developer_menu_ac3_support), + getString(ac3Support ? R.string.developer_menu_ac3_support_yes + : R.string.developer_menu_ac3_support_no))); + return items; + } +} diff --git a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java index dec017a8..06415c21 100644 --- a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java +++ b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java @@ -156,11 +156,8 @@ public class PipInputSelectorFragment extends SideFragment { // If this input shares the same parent with the current main input, you cannot select // it. (E.g. two HDMI CEC devices that are connected to HDMI port 1 through an A/V // receiver.) - PipInput pipInput = mPipInputManager.getPipInput(getMainActivity().getCurrentChannel()); - if (pipInput == null) { - return false; - } - TvInputInfo mainInputInfo = pipInput.getInputInfo(); + TvInputInfo mainInputInfo = mPipInputManager.getPipInput( + getMainActivity().getCurrentChannel()).getInputInfo(); TvInputInfo pipInputInfo = mPipInput.getInputInfo(); return mainInputInfo == null || pipInputInfo == null || !TextUtils.equals(mainInputInfo.getId(), pipInputInfo.getId()) diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java index 6d606014..6b5b2584 100644 --- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java @@ -16,9 +16,13 @@ package com.android.tv.ui.sidepanel; +import android.content.res.Resources; +import android.os.Build; +import android.provider.Settings; import android.view.View; import android.widget.Toast; +import com.android.tv.Features; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; @@ -151,6 +155,19 @@ public class SettingsFragment extends SideFragment { if (LicenseUtils.hasLicenses(activity.getAssets())) { items.add(new LicenseActionItem(activity)); } + boolean developerOptionEnabled = Settings.Secure.getInt(getActivity().getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0) != 0; + if (Features.DEVELOPER_OPTION.isEnabled(getActivity()) && developerOptionEnabled + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Resources res = getActivity().getResources(); + items.add(new ActionItem(res.getString(R.string.side_panel_title_developer)) { + @Override + protected void onSelected() { + getMainActivity().getOverlayManager().getSideFragmentManager().show( + new DeveloperFragment()); + } + }); + } // Show version. items.add(new SimpleItem(getString(R.string.settings_menu_version), ((TvApplication) activity.getApplicationContext()).getVersionName())); diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java index 6bc47939..7ec28bb8 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java @@ -18,7 +18,6 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.util.ArrayMap; import android.util.SparseIntArray; import android.view.View; import android.widget.CompoundButton; @@ -42,7 +41,6 @@ import com.android.tv.util.TvSettings.ContentRatingLevel; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; public class RatingsFragment extends SideFragment { private static final SparseIntArray sLevelResourceIdMap; @@ -75,9 +73,6 @@ public class RatingsFragment extends SideFragment { } private final List<RatingLevelItem> mRatingLevelItems = new ArrayList<>(); - // A map from the rating system ID string to RatingItem objects. - private final Map<String, List<RatingItem>> mContentRatingSystemItemMap = new ArrayMap<>(); - private ParentalControlSettings mParentalControlSettings; public static String getDescription(MainActivity tvActivity) { @ContentRatingLevel int currentLevel = @@ -109,32 +104,18 @@ public class RatingsFragment extends SideFragment { updateRatingLevels(); items.addAll(mRatingLevelItems); - mContentRatingSystemItemMap.clear(); - List<ContentRatingSystem> contentRatingSystems = getMainActivity().getContentRatingsManager().getContentRatingSystems(); Collections.sort(contentRatingSystems, ContentRatingSystem.DISPLAY_NAME_COMPARATOR); for (ContentRatingSystem s : contentRatingSystems) { - if (mParentalControlSettings.isContentRatingSystemEnabled(s)) { - List<RatingItem> ratingItems = new ArrayList<>(); - boolean hasSubRating = false; + if (getMainActivity().getParentalControlSettings().isContentRatingSystemEnabled(s)) { items.add(new DividerItem(s.getDisplayName())); for (Rating rating : s.getRatings()) { - RatingItem item = rating.getSubRatings().isEmpty() ? + RatingItem item = rating.getSubRatings().size() == 0 ? new RatingItem(s, rating) : new RatingWithSubItem(s, rating); items.add(item); - if (rating.getSubRatings().isEmpty()) { - ratingItems.add(item); - } else { - hasSubRating = true; - } - } - // Only include rating systems that don't contain any sub ratings in the map for - // simplicity. - if (!hasSubRating) { - mContentRatingSystemItemMap.put(s.getId(), ratingItems); } } } @@ -150,8 +131,7 @@ public class RatingsFragment extends SideFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mParentalControlSettings = getMainActivity().getParentalControlSettings(); - mParentalControlSettings.loadRatings(); + getMainActivity().getParentalControlSettings().loadRatings(); } @Override @@ -166,27 +146,13 @@ public class RatingsFragment extends SideFragment { } private void updateRatingLevels() { - @ContentRatingLevel int ratingLevel = mParentalControlSettings.getContentRatingLevel(); + @ContentRatingLevel int ratingLevel = + getMainActivity().getParentalControlSettings().getContentRatingLevel(); for (RatingLevelItem ratingLevelItem : mRatingLevelItems) { ratingLevelItem.setChecked(ratingLevel == ratingLevelItem.mRatingLevel); } } - private void updateDependentRatingItems(ContentRatingSystem.Order order, - int selectedRatingOrderIndex, String contentRatingSystemId, boolean isChecked) { - List<RatingItem> ratingItems = mContentRatingSystemItemMap.get(contentRatingSystemId); - if (ratingItems != null) { - for (RatingItem item : ratingItems) { - int ratingOrderIndex = item.getRatingOrderIndex(order); - if (ratingOrderIndex != -1 - && ((ratingOrderIndex > selectedRatingOrderIndex && isChecked) - || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) { - item.setRatingBlocked(isChecked); - } - } - } - } - private class RatingLevelItem extends RadioButtonItem { private final int mRatingLevel; @@ -200,7 +166,7 @@ public class RatingsFragment extends SideFragment { @Override protected void onSelected() { super.onSelected(); - mParentalControlSettings.setContentRatingLevel( + getMainActivity().getParentalControlSettings().setContentRatingLevel( getMainActivity().getContentRatingsManager(), mRatingLevel); notifyItemsChanged(mRatingLevelItems.size()); } @@ -211,21 +177,12 @@ public class RatingsFragment extends SideFragment { protected final Rating mRating; private final Drawable mIcon; private CompoundButton mCompoundButton; - private final List<ContentRatingSystem.Order> mOrders = new ArrayList<>(); - private final List<Integer> mOrderIndexes = new ArrayList<>(); private RatingItem(ContentRatingSystem contentRatingSystem, Rating rating) { super(rating.getTitle(), rating.getDescription()); mContentRatingSystem = contentRatingSystem; mRating = rating; mIcon = rating.getIcon(); - for (ContentRatingSystem.Order order : mContentRatingSystem.getOrders()) { - int orderIndex = order.getRatingIndex(mRating); - if (orderIndex != -1) { - mOrders.add(order); - mOrderIndexes.add(orderIndex); - } - } } @Override @@ -254,21 +211,17 @@ public class RatingsFragment extends SideFragment { protected void onUpdate() { super.onUpdate(); mCompoundButton.setButtonDrawable(getButtonDrawable()); - setChecked(mParentalControlSettings.isRatingBlocked(mContentRatingSystem, mRating)); + setChecked(getMainActivity().getParentalControlSettings().isRatingBlocked( + mContentRatingSystem, mRating)); } @Override protected void onSelected() { super.onSelected(); - if (mParentalControlSettings.setRatingBlocked( - mContentRatingSystem, mRating, isChecked())) { + if (getMainActivity().getParentalControlSettings() + .setRatingBlocked(mContentRatingSystem, mRating, isChecked())) { updateRatingLevels(); } - // Automatically check/uncheck dependent ratings. - for (int i = 0; i < mOrders.size(); i++) { - updateDependentRatingItems(mOrders.get(i), mOrderIndexes.get(i), - mContentRatingSystem.getId(), isChecked()); - } } @Override @@ -279,19 +232,6 @@ public class RatingsFragment extends SideFragment { protected int getButtonDrawable() { return R.drawable.btn_lock_material_anim; } - - private int getRatingOrderIndex(ContentRatingSystem.Order order) { - int orderIndex = mOrders.indexOf(order); - return orderIndex == -1 ? -1 : mOrderIndexes.get(orderIndex); - } - - private void setRatingBlocked(boolean isChecked) { - if (isChecked() == isChecked) { - return; - } - mParentalControlSettings.setRatingBlocked(mContentRatingSystem, mRating, isChecked); - notifyUpdated(); - } } private class RatingWithSubItem extends RatingItem { @@ -307,7 +247,7 @@ public class RatingsFragment extends SideFragment { @Override protected int getButtonDrawable() { - int blockedStatus = mParentalControlSettings.getBlockedStatus( + int blockedStatus = getMainActivity().getParentalControlSettings().getBlockedStatus( mContentRatingSystem, mRating); if (blockedStatus == ParentalControlSettings.RATING_BLOCKED) { return R.drawable.btn_lock_material; diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java index 7ac293fc..9f440533 100644 --- a/src/com/android/tv/util/AsyncDbTask.java +++ b/src/com/android/tv/util/AsyncDbTask.java @@ -22,12 +22,10 @@ import android.media.tv.TvContract; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.MainThread; -import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; @@ -42,10 +40,6 @@ import java.util.concurrent.RejectedExecutionException; * * <p>Instances of this class should only be executed this using {@link * #executeOnDbThread(Object[])}. - * - * @param <Params> the type of the parameters sent to the task upon execution. - * @param <Progress> the type of the progress units published during the background computation. - * @param <Result> the type of the result of the background computation. */ public abstract class AsyncDbTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> { @@ -85,7 +79,7 @@ public abstract class AsyncDbTask<Params, Progress, Result> * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} * which is implemented by subclasses. * - * @param <Result> the type of result returned by {@link #onQuery(Cursor)} + * @param <Result> The type of result returned by {@link #onQuery(Cursor)} */ public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> { private final ContentResolver mContentResolver; @@ -109,10 +103,9 @@ public abstract class AsyncDbTask<Params, Progress, Result> @Override protected final Result doInBackground(Void... params) { if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) { - IllegalStateException e = new IllegalStateException(this - + " should only be executed using executeOnDbThread, " - + "but it was called on thread " - + Thread.currentThread()); + IllegalStateException e = new IllegalStateException( + this + " should only be executed using executeOnDbThread, " + + "but it was called on thread " + Thread.currentThread()); Log.w(TAG, e); if (DEBUG) { throw e; @@ -144,8 +137,8 @@ public abstract class AsyncDbTask<Params, Progress, Result> } return null; } - } catch (Exception e) { - SoftPreconditions.warn(TAG, null, "Error querying " + this, e); + } catch (SecurityException e) { + Log.d(TAG, "Security exception during query", e); return null; } } @@ -168,10 +161,8 @@ public abstract class AsyncDbTask<Params, Progress, Result> * Returns the result of a query as an {@link List} of {@code T}. * * <p>Subclasses must implement {@link #fromCursor(Cursor)}. - * - * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)} */ - public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> { + public static abstract class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> { public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) { @@ -210,56 +201,9 @@ public abstract class AsyncDbTask<Params, Progress, Result> } /** - * Returns the result of a query as a single instance of {@code T}. - * - * <p>Subclasses must implement {@link #fromCursor(Cursor)}. - */ - public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> { - - public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy) { - super(contentResolver, uri, projection, selection, selectionArgs, orderBy); - } - - @Override - protected final T onQuery(Cursor c) { - if (c.moveToNext()) { - if (isCancelled()) { - // This is guaranteed to never call onPostExecute because the task is canceled. - return null; - } - T result = fromCursor(c); - if (c.moveToNext()) { - Log.w(TAG, "More than one result for found for " + this); - } - return result; - } else { - if (DEBUG) { - Log.v(TAG, "No result for found for " + this); - } - return null; - } - - } - - /** - * Return a single instance of {@code T} from the cursor. - * - * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link - * #onQuery(Cursor)}. - * - * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)} - * - * @param c The cursor with the values to create T from. - */ - @WorkerThread - protected abstract T fromCursor(Cursor c); - } - - /** * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. */ - public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> { + public static abstract class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> { public AsyncChannelQueryTask(ContentResolver contentResolver) { super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION, @@ -283,19 +227,16 @@ public abstract class AsyncDbTask<Params, Progress, Result> /** * Gets an {@link List} of {@link Program}s for a given channel and period {@link - * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is - * {@code null}, then all the programs is queried. + * TvContract#buildProgramsUriForChannel(long, long, long)}. */ public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> { protected final Range<Long> mPeriod; protected final long mChannelId; public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, - @Nullable Range<Long> period) { - super(contentResolver, period == null - ? TvContract.buildProgramsUriForChannel(channelId) - : TvContract.buildProgramsUriForChannel(channelId, period.getLower(), - period.getUpper()), + Range<Long> period) { + super(contentResolver, TvContract + .buildProgramsUriForChannel(channelId, period.getLower(), period.getUpper()), Program.PROJECTION, null, null, null); mPeriod = period; mChannelId = channelId; diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java index ed0fd54d..59c4983b 100644 --- a/src/com/android/tv/util/ImageLoader.java +++ b/src/com/android/tv/util/ImageLoader.java @@ -28,23 +28,18 @@ import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.annotation.WorkerThread; -import android.util.ArraySet; import android.util.Log; import com.android.tv.R; +import com.android.tv.common.CollectionUtils; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** * This class wraps up completing some arbitrary long running work when loading a bitmap. It @@ -54,39 +49,6 @@ public final class ImageLoader { private static final String TAG = "ImageLoader"; private static final boolean DEBUG = false; - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - // We want at least 2 threads and at most 4 threads in the core pool, - // preferring to have 1 less than the CPU count to avoid saturating - // the CPU with background work - private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); - private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; - private static final int KEEP_ALIVE_SECONDS = 30; - - private static final ThreadFactory sThreadFactory = new NamedThreadFactory("ImageLoader"); - - private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>( - 128); - - /** - * An private {@link Executor} that can be used to execute tasks in parallel. - * - * <p>{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} - * Since we do a lot of concurrent image loading we can exhaust a thread pool. - * ImageLoader catches the error, and just leaves the image blank. - * However other tasks will fail and crash the application. - * - * <p>Using a separate thread pool prevents image loading from causing other tasks to fail. - */ - private static final Executor IMAGE_THREAD_POOL_EXECUTOR; - - static { - ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, - MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, - sThreadFactory); - threadPoolExecutor.allowCoreThreadTimeOut(true); - IMAGE_THREAD_POOL_EXECUTOR = threadPoolExecutor; - } - private static Handler sMainHandler; /** @@ -186,7 +148,7 @@ public final class ImageLoader { Log.d(TAG, "loadBitmap() " + uriString); } return doLoadBitmap(context, uriString, maxWidth, maxHeight, callback, - IMAGE_THREAD_POOL_EXECUTOR); + AsyncTask.THREAD_POOL_EXECUTOR); } private static boolean doLoadBitmap(Context context, String uriString, @@ -217,7 +179,7 @@ public final class ImageLoader { if (DEBUG) { Log.d(TAG, "loadBitmap() " + loadBitmapTask); } - return doLoadBitmap(callback, IMAGE_THREAD_POOL_EXECUTOR, loadBitmapTask); + return doLoadBitmap(callback, AsyncTask.THREAD_POOL_EXECUTOR, loadBitmapTask); } /** @@ -264,7 +226,7 @@ public final class ImageLoader { protected final Context mAppContext; protected final int mMaxWidth; protected final int mMaxHeight; - private final Set<ImageLoaderCallback> mCallbacks = new ArraySet<>(); + private final Set<ImageLoaderCallback> mCallbacks = CollectionUtils.createSmallSet(); private final ImageCache mImageCache; private final String mKey; diff --git a/src/com/android/tv/util/MultiLongSparseArray.java b/src/com/android/tv/util/MultiLongSparseArray.java index 1d5fa80b..7ed72d61 100644 --- a/src/com/android/tv/util/MultiLongSparseArray.java +++ b/src/com/android/tv/util/MultiLongSparseArray.java @@ -17,9 +17,10 @@ package com.android.tv.util; import android.support.annotation.VisibleForTesting; -import android.util.ArraySet; import android.util.LongSparseArray; +import com.android.tv.common.CollectionUtils; + import java.util.Collections; import java.util.Set; @@ -104,7 +105,7 @@ public class MultiLongSparseArray<T> { private Set<T> getEmptySet() { if (mEmptyIndex < 0) { - return new ArraySet<>(); + return CollectionUtils.createSmallSet(); } Set<T> emptySet = mEmptySets[mEmptyIndex]; mEmptySets[mEmptyIndex--] = null; diff --git a/src/com/android/tv/util/NetworkUtils.java b/src/com/android/tv/util/NetworkUtils.java deleted file mode 100644 index ed3ce383..00000000 --- a/src/com/android/tv/util/NetworkUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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.android.tv.util; - -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * A utility class to check the connectivity. - */ -@WorkerThread -public class NetworkUtils { - private static final String GENERATE_204 = "http://clients3.google.com/generate_204"; - - /** - * Checks if the internet connection is available. - */ - public static boolean isNetworkAvailable(@Nullable ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return false; - } - NetworkInfo info = connectivityManager.getActiveNetworkInfo(); - if (info == null || !info.isConnected()) { - return false; - } - HttpURLConnection connection = null; - try { - connection = (HttpURLConnection) new URL(GENERATE_204).openConnection(); - connection.setInstanceFollowRedirects(false); - connection.setDefaultUseCaches(false); - connection.setUseCaches(false); - if (connection.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return true; - } - } catch (IOException e) { - // Does nothing. - } finally { - if (connection != null) { - connection.disconnect(); - } - } - return false; - } - - private NetworkUtils() { } -} diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java index 3dcc324d..582a0c9f 100644 --- a/src/com/android/tv/util/OnboardingUtils.java +++ b/src/com/android/tv/util/OnboardingUtils.java @@ -37,7 +37,7 @@ public final class OnboardingUtils { private static final int ONBOARDING_VERSION = 1; private static final String MERCHANT_COLLECTION_URL_STRING = - "TODO: put a market link to show TV input apps"; + "https://play.google.com/store/apps/collection/promotion_3001bf9_ATV_livechannels"; /** * Intent to show merchant collection in play store. */ @@ -101,7 +101,7 @@ public final class OnboardingUtils { ContentResolver resolver = context.getContentResolver(); try (Cursor c = resolver.query(Channels.CONTENT_URI, new String[] {Channels._ID}, null, null, null)) { - return c != null && c.getCount() != 0; + return c.getCount() != 0; } } diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java index 03bdc681..dddc82b6 100644 --- a/src/com/android/tv/util/PipInputManager.java +++ b/src/com/android/tv/util/PipInputManager.java @@ -20,11 +20,11 @@ import android.content.Context; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; -import android.util.ArraySet; import android.util.Log; import com.android.tv.ChannelTuner; import com.android.tv.R; +import com.android.tv.common.CollectionUtils; import com.android.tv.data.Channel; import java.util.ArrayList; @@ -37,7 +37,6 @@ import java.util.Set; /** * A class that manages inputs for PIP. All tuner inputs are represented to one tuner input for PIP. - * Hidden inputs should not be visible to the users. */ public class PipInputManager { private static final String TAG = "PipInputManager"; @@ -51,7 +50,7 @@ public class PipInputManager { private final ChannelTuner mChannelTuner; private boolean mStarted; private final Map<String, PipInput> mPipInputMap = new HashMap<>(); // inputId -> PipInput - private final Set<Listener> mListeners = new ArraySet<>(); + private final Set<Listener> mListeners = CollectionUtils.createSmallSet(); private final TvInputCallback mTvInputCallback = new TvInputCallback() { @Override @@ -183,53 +182,40 @@ public class PipInputManager { /** * Gets the size of inputs for PIP. * - * <p>The hidden inputs are not counted. - * - * @param availableOnly If {@code true}, it counts only available PIP inputs. Please see {@link + * @param availableOnly If true, it counts only available PIP inputs. Please see {@link * PipInput#isAvailable()} for the details of availability. */ public int getPipInputSize(boolean availableOnly) { - int count = 0; - for (PipInput pipInput : mPipInputMap.values()) { - if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) { - ++count; - } - if (pipInput.isPassthrough()) { - TvInputInfo info = pipInput.getInputInfo(); - // Do not count HDMI ports if a CEC device is directly connected to the port. - if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) { - --count; + if (availableOnly) { + int count = 0; + for (PipInput pipInput : mPipInputMap.values()) { + if (pipInput.isAvailable()) { + ++count; } } + return count; + } else { + return mPipInputMap.size(); } - return count; } /** - * Gets the list of inputs for PIP.. - * - * <p>The hidden inputs are excluded. + * Gets the list of inputs for PIP. * * @param availableOnly If true, it returns only available PIP inputs. Please see {@link * PipInput#isAvailable()} for the details of availability. */ public List<PipInput> getPipInputList(boolean availableOnly) { - List<PipInput> pipInputs = new ArrayList<>(); - List<PipInput> removeInputs = new ArrayList<>(); - for (PipInput pipInput : mPipInputMap.values()) { - if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) { - pipInputs.add(pipInput); - } - if (pipInput.isPassthrough()) { - TvInputInfo info = pipInput.getInputInfo(); - // Do not show HDMI ports if a CEC device is directly connected to the port. - if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) { - removeInputs.add(mPipInputMap.get(info.getParentId())); + List<PipInput> pipInputs; + if (availableOnly) { + pipInputs = new ArrayList<>(); + for (PipInput pipInput : mPipInputMap.values()) { + if (pipInput.mAvailable) { + pipInputs.add(pipInput); } } - } - if (!removeInputs.isEmpty()) { - pipInputs.removeAll(removeInputs); + } else { + pipInputs = new ArrayList<>(mPipInputMap.values()); } Collections.sort(pipInputs, new Comparator<PipInput>() { @Override @@ -339,9 +325,9 @@ public class PipInputManager { } /** - * Returns {@code true}, if the input is available for PIP. If a channel of an input is - * already played or an input is not connected state or there is no browsable channel, the - * input is unavailable. + * Returns true, if the input is available for PIP. If a channel of an input is already + * played or an input is not connected state or there is no browsable channel, the input + * is unavailable. */ public boolean isAvailable() { return mAvailable; @@ -421,10 +407,5 @@ public class PipInputManager { } } } - - private boolean isHidden() { - // mInputInfo is null for the tuner input and it's always visible. - return mInputInfo != null && mInputInfo.isHidden(mContext); - } } } diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java index 5e65715e..2a006f9e 100644 --- a/src/com/android/tv/util/RecurringRunner.java +++ b/src/com/android/tv/util/RecurringRunner.java @@ -24,7 +24,6 @@ import android.support.annotation.WorkerThread; import android.util.Log; import com.android.tv.common.SharedPreferencesUtils; -import com.android.tv.common.SoftPreconditions; import java.util.Date; diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java index 6d24d5bd..d337139b 100644 --- a/src/com/android/tv/util/SetupUtils.java +++ b/src/com/android/tv/util/SetupUtils.java @@ -27,13 +27,11 @@ import android.os.Build; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.text.TextUtils; -import android.util.ArraySet; import android.util.Log; import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.CollectionUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; @@ -69,13 +67,13 @@ public class SetupUtils { private SetupUtils(TvApplication tvApplication) { mTvApplication = tvApplication; mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication); - mSetUpInputs = new ArraySet<>(); + mSetUpInputs = CollectionUtils.createSmallSet(); mSetUpInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.<String>emptySet())); - mKnownInputs = new ArraySet<>(); + mKnownInputs = CollectionUtils.createSmallSet(); mKnownInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, Collections.<String>emptySet())); - mRecognizedInputs = new ArraySet<>(); + mRecognizedInputs = CollectionUtils.createSmallSet(); mRecognizedInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs)); mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true); @@ -264,25 +262,28 @@ public class SetupUtils { * @param context The Context used for granting permission. */ public static void grantEpgPermissionToSetUpPackages(Context context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Can't grant permission. - return; - } - - // Find all already-verified packages. - Set<String> setUpPackages = new HashSet<>(); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - for (String input : sp.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.EMPTY_SET)) { - if (!TextUtils.isEmpty(input)) { - ComponentName componentName = ComponentName.unflattenFromString(input); - if (componentName != null) { - setUpPackages.add(componentName.getPackageName()); + // TvProvider allows granting of Uri permissions starting from MNC. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); + Set<String> setUpInputs = new HashSet<>(sharedPreferences.getStringSet( + PREF_KEY_SET_UP_INPUTS, new HashSet<String>())); + Set<String> setUpPackages = new HashSet<>(); + for (String input : setUpInputs) { + ComponentName componentName = null; + try { + componentName = ComponentName.unflattenFromString(input); + } catch (Exception e) { + Log.w(TAG, "Failed to unflatten string to component name (" + input + ")", e); } + if (componentName == null) { + continue; + } + setUpPackages.add(componentName.getPackageName()); + } + for (String packageName : setUpPackages) { + grantEpgPermission(context, packageName); } - } - - for (String packageName : setUpPackages) { - grantEpgPermission(context, packageName); } } @@ -345,7 +346,7 @@ public class SetupUtils { } mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs) .putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) - .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply(); + .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs).apply(); } } @@ -359,7 +360,7 @@ public class SetupUtils { if (!mRecognizedInputs.contains(inputId)) { Log.i(TAG, "An unrecognized input's setup has been done. inputId=" + inputId); mRecognizedInputs.add(inputId); - mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) + mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs) .apply(); } if (!mKnownInputs.contains(inputId)) { diff --git a/src/com/android/tv/util/SoftPreconditions.java b/src/com/android/tv/util/SoftPreconditions.java new file mode 100644 index 00000000..3643fca4 --- /dev/null +++ b/src/com/android/tv/util/SoftPreconditions.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015 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.android.tv.util; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import com.android.tv.common.BuildConfig; +import com.android.tv.common.feature.Feature; + +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. + * + * <p>{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and + * logs a warning when it is false. + * + * <p>This is based on com.android.internal.util.Preconditions. + */ +public final class SoftPreconditions { + private static final String TAG = "SoftPreconditions"; + + /** + * Throws or logs if an expression involving the parameter of the calling + * method is not true. + * + * @param expression a boolean expression + * @param tag Used to identify the source of a log message. It usually + * identifies the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @throws IllegalArgumentException if {@code expression} is true + */ + public static void checkArgument(final boolean expression, String tag, String msg) { + if (!expression) { + warn(tag, "Illegal argument", msg, new IllegalArgumentException(msg)); + } + } + + /** + * Throws or logs if an expression involving the parameter of the calling + * method is not true. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is true + */ + public static void checkArgument(final boolean expression) { + checkArgument(expression, null, null); + } + + /** + * Throws or logs if an and object is null. + * + * @param reference an object reference + * @param tag Used to identify the source of a log message. It usually + * identifies the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @return true if the object is null + * @throws NullPointerException if {@code reference} is null + */ + public static <T> T checkNotNull(final T reference, String tag, String msg) { + if (reference == null) { + warn(tag, "Null Pointer", msg, new NullPointerException(msg)); + } + return reference; + } + + /** + * Throws or logs if an and object is null. + * + * @param reference an object reference + * @return true if the object is null + * @throws NullPointerException if {@code reference} is null + */ + public static <T> T checkNotNull(final T reference) { + return checkNotNull(reference, null, null); + } + + /** + * Throws or logs if an expression involving the state of the calling + * instance, but not involving any parameters to the calling method is not true. + * + * @param expression a boolean expression + * @param tag Used to identify the source of a log message. It usually + * identifies the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @throws IllegalStateException if {@code expression} is true + */ + public static void checkState(final boolean expression, String tag, String msg) { + if (!expression) { + warn(tag, "Illegal State", msg, new IllegalStateException(msg)); + } + } + + /** + * Throws or logs if an expression involving the state of the calling + * instance, but not involving any parameters to the calling method is not true. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is true + */ + public static void checkState(final boolean expression) { + checkState(expression, null, null); + } + + /** + * Throws or logs if the Feature is not enabled + * + * @param context an android context + * @param feature the required feature + * @param tag used to identify the source of a log message. It usually + * identifies the class or activity where the log call occurs + * @throws IllegalStateException if {@code feature} is not enabled + */ + public static void checkFeatureEnabled(Context context, Feature feature, String tag) { + checkState(feature.isEnabled(context), tag, feature.toString()); + } + + /** + * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning. + * + * @param tag Used to identify the source of a log message. It usually + * identifies the class or activity where the log call occurs. + * @param msg The message you would like logged + * @param e The exception to throw + */ + public static void warn(String tag, String prefix, String msg, RuntimeException e) + throws RuntimeException{ + if (BuildConfig.ENG) { + throw e; + } else { + if (TextUtils.isEmpty(tag)) { + tag = TAG; + } + String logMessage; + if (TextUtils.isEmpty(msg)) { + logMessage = prefix; + } else if (TextUtils.isEmpty(prefix)) { + logMessage = msg; + } else { + logMessage = prefix + ": " + msg; + } + Log.w(tag, logMessage, e); + } + } + + private SoftPreconditions() { + } +} diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java index 235161b6..1dc70fd5 100644 --- a/src/com/android/tv/util/SystemProperties.java +++ b/src/com/android/tv/util/SystemProperties.java @@ -58,6 +58,13 @@ public final class SystemProperties { public static final BooleanSystemProperty USE_TRACKER = new BooleanSystemProperty( "tv_use_tracker", true); + /** + * Selects using {@link com.android.tv.dvr.DvrDataManagerInMemoryImpl} + * instead of {@link com.android.tv.dvr.DvrDataManagerImpl} + */ + public static final BooleanSystemProperty USE_IN_MEMORY_DVR_DB = new BooleanSystemProperty( + "tv_use_in_memory_dvr_db", false); // STOPSHIP(DVR) + static { updateSystemProperties(); } diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index b4149637..250ca430 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -26,7 +26,6 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; -import com.android.tv.common.SoftPreconditions; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java index a763fe58..44d601c5 100644 --- a/src/com/android/tv/util/Utils.java +++ b/src/com/android/tv/util/Utils.java @@ -24,10 +24,10 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.database.Cursor; +import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.media.tv.TvInputInfo; @@ -42,16 +42,18 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.view.View; -import android.widget.Toast; +import com.android.tv.Features; import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; +import com.android.usbtuner.tvinput.UsbTunerTvInputService; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -334,19 +336,6 @@ public class Utils { return ""; } - public static String getAspectRatioString(float videoDisplayAspectRatio) { - if (videoDisplayAspectRatio <= 0) { - return ""; - } - - for (AspectRatio ratio : AspectRatio.values()) { - if (Math.abs((float) ratio.width / ratio.height - videoDisplayAspectRatio) < 0.05f) { - return ratio.toString(); - } - } - return ""; - } - public static int getVideoDefinitionLevelFromSize(int width, int height) { if (width >= VIDEO_ULTRA_HD_WIDTH && height >= VIDEO_ULTRA_HD_HEIGHT) { return StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD; @@ -620,46 +609,67 @@ public class Utils { } /** - * Returns a localized version of the text resource specified by resourceId. + * Returns input ID of {@link UsbTunerTvInputService}. */ - public static CharSequence getTextForLocale(Context context, Locale locale, int resourceId) { - if (locale.equals(context.getResources().getConfiguration().locale)) { - return context.getText(resourceId); + @Nullable + public static String getUsbTunerInputId(Context context) { + if (!Features.USB_TUNER.isEnabled(context)) { + return null; } - Configuration config = new Configuration(context.getResources().getConfiguration()); - config.setLocale(locale); - return context.createConfigurationContext(config).getText(resourceId); + return TvContract.buildInputId(new ComponentName(context.getPackageName(), + UsbTunerTvInputService.class.getName())); } /** - * Returns the internal TV inputs. + * Returns {@link TvInputInfo} object of {@link UsbTunerTvInputService}. */ - public static List<TvInputInfo> getInternalTvInputs(Context context, boolean tunerInputOnly) { - List<TvInputInfo> inputs = new ArrayList<>(); - String contextPackageName = context.getPackageName(); - for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper() - .getTvInputInfos(true, tunerInputOnly)) { - if (contextPackageName.equals(ComponentName.unflattenFromString(input.getId()) - .getPackageName())) { - inputs.add(input); - } + @Nullable + public static TvInputInfo getUsbTunerInputInfo(Context context) { + if (!Features.USB_TUNER.isEnabled(context)) { + return null; } - return inputs; + TvInputManagerHelper helper = TvApplication.getSingletons(context) + .getTvInputManagerHelper(); + return helper.getTvInputInfo(getUsbTunerInputId(context)); } - /** - * Checks whether the input is internal or not. - */ - public static boolean isInternalTvInput(Context context, String inputId) { - return context.getPackageName().equals(ComponentName.unflattenFromString(inputId) - .getPackageName()); - } + private static final class SyncRunnable implements Runnable { + private final Runnable mTarget; + private boolean mComplete; - /** - * Shows a toast message to notice that the current feature is a developer feature. - */ - public static void showToastMessageForDeveloperFeature(Context context) { - Toast.makeText(context, "This feature is for developer preview.", Toast.LENGTH_SHORT) - .show(); + public SyncRunnable(Runnable target) { + mTarget = target; + } + + @Override + public void run() { + try { + mTarget.run(); + } finally { + synchronized (this) { + mComplete = true; + notifyAll(); + } + } + } + + public void waitForComplete() { + boolean interrupted = false; + synchronized (this) { + try { + while (!mComplete) { + try { + wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + } } } diff --git a/src/com/android/usbtuner/UsbInputController.java b/src/com/android/usbtuner/UsbInputController.java index f0982eb5..6d6fccc2 100644 --- a/src/com/android/usbtuner/UsbInputController.java +++ b/src/com/android/usbtuner/UsbInputController.java @@ -23,14 +23,10 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; -import android.media.tv.TvInputService; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.support.v4.os.BuildCompat; import android.util.Log; import com.android.tv.Features; @@ -48,7 +44,7 @@ import java.util.Map; * to update the connection status of the supported USB TV tuners. */ public class UsbInputController extends BroadcastReceiver { - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final String TAG = "UsbInputController"; private static final TunerDevice[] TUNER_DEVICES = { @@ -62,7 +58,7 @@ public class UsbInputController extends BroadcastReceiver { private static final long DVB_DRIVER_CHECK_DELAY_MS = 300; private DvbDeviceAccessor mDvbDeviceAccessor; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { + private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -159,10 +155,6 @@ public class UsbInputController extends BroadcastReceiver { PackageManager pm = context.getPackageManager(); ComponentName USBTUNER = new ComponentName(context, UsbTunerTvInputService.class); - // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling - // TvActivity, the following pm.setComponentEnabledSetting doesn't work. - ((TvApplication) context.getApplicationContext()).handleInputCountChanged( - true, enabled, true); // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only // when the LiveChannels app is active since we don't want to kill the running app. @@ -173,17 +165,10 @@ public class UsbInputController extends BroadcastReceiver { if (newState != pm.getComponentEnabledSetting(USBTUNER)) { // Send/cancel the USB tuner TV input setup recommendation card. TunerSetupActivity.onTvInputEnabled(context, enabled); + // Enable/disable the USB tuner TV input. pm.setComponentEnabledSetting(USBTUNER, newState, flags); if (DEBUG) Log.d(TAG, "Status updated:" + enabled); } - if (enabled && BuildCompat.isAtLeastN()) { - TvInputInfo info = mDvbDeviceAccessor.buildTvInputInfo(context); - if (info != null) { - Log.i(TAG, "TvInputInfo updated: " + info.toString()); - ((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE)) - .updateTvInputInfo(info); - } - } } } |