aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYoungsang Cho <youngsang@google.com>2016-05-11 01:20:46 +0000
committerYoungsang Cho <youngsang@google.com>2016-05-11 01:20:46 +0000
commit3a72b93e554bd22a5c64e71a6956d9604ce05108 (patch)
tree3d9a5451eb5ac85f8c18619c282eb82eb8fa2ddf /src
parent369b6a409204a9b2a95f7ba575d7c3b7bdc94ab7 (diff)
downloadTV-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')
-rw-r--r--src/com/android/tv/ChannelTuner.java6
-rw-r--r--src/com/android/tv/Features.java63
-rw-r--r--src/com/android/tv/MainActivity.java584
-rw-r--r--src/com/android/tv/MainActivityWrapper.java4
-rw-r--r--src/com/android/tv/SetupPassthroughActivity.java2
-rw-r--r--src/com/android/tv/TimeShiftManager.java204
-rw-r--r--src/com/android/tv/TvApplication.java65
-rw-r--r--src/com/android/tv/TvOptionsManager.java13
-rw-r--r--src/com/android/tv/analytics/SendConfigInfoRunnable.java4
-rw-r--r--src/com/android/tv/data/Channel.java39
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java19
-rw-r--r--src/com/android/tv/data/GenreItems.java39
-rw-r--r--src/com/android/tv/data/Program.java153
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java49
-rw-r--r--src/com/android/tv/data/StreamInfo.java1
-rw-r--r--src/com/android/tv/data/WatchedHistoryManager.java9
-rw-r--r--src/com/android/tv/data/epg/EpgFetcher.java356
-rw-r--r--src/com/android/tv/data/epg/EpgReader.java53
-rw-r--r--src/com/android/tv/data/epg/StubEpgReader.java53
-rw-r--r--src/com/android/tv/dialog/FullscreenDialogFragment.java5
-rw-r--r--src/com/android/tv/dvr/BaseDvrDataManager.java135
-rw-r--r--src/com/android/tv/dvr/DvrDataManager.java73
-rw-r--r--src/com/android/tv/dvr/DvrDataManagerImpl.java308
-rw-r--r--src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java144
-rw-r--r--src/com/android/tv/dvr/DvrManager.java116
-rw-r--r--src/com/android/tv/dvr/DvrPlayActivity.java10
-rw-r--r--src/com/android/tv/dvr/DvrRecordingService.java20
-rw-r--r--src/com/android/tv/dvr/DvrSessionManager.java117
-rw-r--r--src/com/android/tv/dvr/Recording.java (renamed from src/com/android/tv/dvr/ScheduledRecording.java)225
-rw-r--r--src/com/android/tv/dvr/RecordingTask.java208
-rw-r--r--src/com/android/tv/dvr/ScheduledProgramReaper.java53
-rw-r--r--src/com/android/tv/dvr/Scheduler.java51
-rw-r--r--src/com/android/tv/dvr/SeasonRecording.java2
-rw-r--r--src/com/android/tv/dvr/WritableDvrDataManager.java6
-rw-r--r--src/com/android/tv/dvr/provider/AsyncDvrDbTask.java81
-rw-r--r--src/com/android/tv/dvr/provider/DvrContract.java66
-rw-r--r--src/com/android/tv/dvr/provider/DvrDatabaseHelper.java101
-rw-r--r--src/com/android/tv/dvr/ui/DvrBrowseFragment.java102
-rw-r--r--src/com/android/tv/dvr/ui/DvrDialogFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java73
-rw-r--r--src/com/android/tv/dvr/ui/DvrRecordConflictFragment.java82
-rw-r--r--src/com/android/tv/dvr/ui/DvrRecordDeleteFragment.java48
-rw-r--r--src/com/android/tv/dvr/ui/DvrRecordScheduleFragment.java70
-rw-r--r--src/com/android/tv/dvr/ui/EmptyHolder.java27
-rw-r--r--src/com/android/tv/dvr/ui/EmptyItemPresenter.java66
-rw-r--r--src/com/android/tv/dvr/ui/GridItemPresenter.java165
-rw-r--r--src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java30
-rw-r--r--src/com/android/tv/dvr/ui/RecordedProgramPresenter.java121
-rw-r--r--src/com/android/tv/dvr/ui/RecordedProgramsAdapter.java65
-rw-r--r--src/com/android/tv/dvr/ui/RecordingCardView.java115
-rw-r--r--src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java171
-rw-r--r--src/com/android/tv/dvr/ui/ScheduledRecordingsAdapter.java85
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java193
-rw-r--r--src/com/android/tv/guide/ProgramGrid.java60
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java19
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java136
-rw-r--r--src/com/android/tv/guide/ProgramListAdapter.java48
-rw-r--r--src/com/android/tv/guide/ProgramManager.java179
-rw-r--r--src/com/android/tv/guide/ProgramRow.java2
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java92
-rw-r--r--src/com/android/tv/guide/TimelineRow.java4
-rw-r--r--src/com/android/tv/menu/ActionCardView.java3
-rw-r--r--src/com/android/tv/menu/BaseCardView.java3
-rw-r--r--src/com/android/tv/menu/ChannelsPosterPrefetcher.java2
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java63
-rw-r--r--src/com/android/tv/menu/ItemListRowView.java8
-rw-r--r--src/com/android/tv/menu/Menu.java4
-rw-r--r--src/com/android/tv/menu/MenuAction.java7
-rw-r--r--src/com/android/tv/menu/MenuLayoutManager.java18
-rw-r--r--src/com/android/tv/menu/MenuView.java7
-rw-r--r--src/com/android/tv/menu/PlayControlsRowView.java42
-rw-r--r--src/com/android/tv/menu/RecordCardView.java189
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java36
-rw-r--r--src/com/android/tv/onboarding/OnboardingActivity.java55
-rw-r--r--src/com/android/tv/onboarding/SetupSourcesFragment.java33
-rw-r--r--src/com/android/tv/onboarding/WelcomeFragment.java82
-rw-r--r--src/com/android/tv/parental/ContentRatingSystem.java13
-rw-r--r--src/com/android/tv/receiver/BootCompletedReceiver.java4
-rw-r--r--src/com/android/tv/receiver/GlobalKeyReceiver.java21
-rw-r--r--src/com/android/tv/receiver/PackageIntentsReceiver.java41
-rw-r--r--src/com/android/tv/recommendation/NotificationService.java8
-rw-r--r--src/com/android/tv/recommendation/RecommendationDataManager.java290
-rw-r--r--src/com/android/tv/recommendation/Recommender.java2
-rw-r--r--src/com/android/tv/recommendation/RoutineWatchEvaluator.java27
-rw-r--r--src/com/android/tv/ui/AppLayerTvView.java5
-rw-r--r--src/com/android/tv/ui/BlockScreenView.java241
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java187
-rw-r--r--src/com/android/tv/ui/DialogUtils.java61
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java2
-rw-r--r--src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java4
-rw-r--r--src/com/android/tv/ui/SelectInputView.java8
-rw-r--r--src/com/android/tv/ui/TunableTvView.java408
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java23
-rw-r--r--src/com/android/tv/ui/TvTransitionManager.java11
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java122
-rw-r--r--src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java24
-rw-r--r--src/com/android/tv/ui/sidepanel/DeveloperFragment.java84
-rw-r--r--src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java7
-rw-r--r--src/com/android/tv/ui/sidepanel/SettingsFragment.java17
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java82
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java83
-rw-r--r--src/com/android/tv/util/ImageLoader.java46
-rw-r--r--src/com/android/tv/util/MultiLongSparseArray.java5
-rw-r--r--src/com/android/tv/util/NetworkUtils.java66
-rw-r--r--src/com/android/tv/util/OnboardingUtils.java4
-rw-r--r--src/com/android/tv/util/PipInputManager.java65
-rw-r--r--src/com/android/tv/util/RecurringRunner.java1
-rw-r--r--src/com/android/tv/util/SetupUtils.java51
-rw-r--r--src/com/android/tv/util/SoftPreconditions.java163
-rw-r--r--src/com/android/tv/util/SystemProperties.java7
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java1
-rw-r--r--src/com/android/tv/util/Utils.java102
-rw-r--r--src/com/android/usbtuner/UsbInputController.java21
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 &lt; 0 || position &gt;= 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);
- }
- }
}
}