aboutsummaryrefslogtreecommitdiff
path: root/common/src
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2016-05-04 11:20:31 -0700
committerNick Chalko <nchalko@google.com>2016-05-04 11:21:28 -0700
commit2e1279b8bbe0603fb4399b25b73121bed5953c46 (patch)
tree83d9dc7e66f196f2da6fb691d5bba5b2ee2b67b9 /common/src
parentadcc7b8a20af38d03a47f8b7c4ab5eed256f085c (diff)
downloadTV-2e1279b8bbe0603fb4399b25b73121bed5953c46.tar.gz
Sync to joey ub-tv-dev at e7fbaa585b1eb7afec05f05032d2e8d99fb595d4
Change-Id: Ib2da547fc0b23c3b504e2fac9c635954fc03060f
Diffstat (limited to 'common/src')
-rw-r--r--common/src/com/android/tv/common/BuildConfig.java10
-rw-r--r--common/src/com/android/tv/common/CollectionUtils.java33
-rw-r--r--common/src/com/android/tv/common/SoftPreconditions.java164
-rw-r--r--common/src/com/android/tv/common/TvContentRatingCache.java4
-rw-r--r--common/src/com/android/tv/common/feature/CommonFeatures.java15
-rw-r--r--common/src/com/android/tv/common/feature/Sdk.java33
-rw-r--r--common/src/com/android/tv/common/recording/PlaybackTvView.java191
-rw-r--r--common/src/com/android/tv/common/recording/RecordedProgram.java760
-rw-r--r--common/src/com/android/tv/common/recording/RecordingTvInputService.java378
-rw-r--r--common/src/com/android/tv/common/recording/RecordingUtils.java50
-rw-r--r--common/src/com/android/tv/common/recording/TvRecording.java384
-rw-r--r--common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java13
-rw-r--r--common/src/com/android/tv/common/ui/setup/leanback/OnboardingFragment.java531
-rw-r--r--common/src/com/android/tv/common/ui/setup/leanback/PagingIndicator.java377
14 files changed, 976 insertions, 1967 deletions
diff --git a/common/src/com/android/tv/common/BuildConfig.java b/common/src/com/android/tv/common/BuildConfig.java
deleted file mode 100644
index 635903c3..00000000
--- a/common/src/com/android/tv/common/BuildConfig.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * TODO: It's manually generated file to fix build breakage.
- * It should be automatically generated.
- */
-package com.android.tv.common;
-public final class BuildConfig {
- public static final boolean DEBUG = false;
- public static final boolean ENG = false;
- private BuildConfig() {}
-}
diff --git a/common/src/com/android/tv/common/CollectionUtils.java b/common/src/com/android/tv/common/CollectionUtils.java
index 4a7a81f2..f81e51a5 100644
--- a/common/src/com/android/tv/common/CollectionUtils.java
+++ b/common/src/com/android/tv/common/CollectionUtils.java
@@ -16,45 +16,12 @@
package com.android.tv.common;
-import android.os.Build;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
/**
* Static utilities for collections
*/
public class CollectionUtils {
- /**
- * Returns a new Set suitable for small data sets.
- *
- * <p>In M and above this is a {@link ArraySet} otherwise it is a {@link HashSet}.
- */
- public static <T> Set<T> createSmallSet() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- return new ArraySet<>();
- } else {
- return new HashSet<>();
- }
- }
-
- /**
- * Returns a new Map suitable for small data sets.
- *
- * <p>In M and above this is a {@link ArrayMap} otherwise it is a {@link HashMap}.
- */
- public static <K, V> Map<K, V> createSmallMap() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- return new ArrayMap<>();
- } else {
- return new HashMap<>();
- }
- }
/**
* Returns an array with the arrays concatenated together.
diff --git a/common/src/com/android/tv/common/SoftPreconditions.java b/common/src/com/android/tv/common/SoftPreconditions.java
new file mode 100644
index 00000000..9b7713f6
--- /dev/null
+++ b/common/src/com/android/tv/common/SoftPreconditions.java
@@ -0,0 +1,164 @@
+/*
+ * 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.common;
+
+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 wrap with a RuntimeException when thrown.
+ */
+ public static void warn(String tag, String prefix, String msg, Exception e)
+ throws RuntimeException {
+ 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;
+ }
+
+ if (BuildConfig.ENG) {
+ throw new RuntimeException(msg, e);
+ } else {
+ Log.w(tag, logMessage, e);
+ }
+ }
+
+ private SoftPreconditions() {
+ }
+}
diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java
index 5ca780e3..7ea86287 100644
--- a/common/src/com/android/tv/common/TvContentRatingCache.java
+++ b/common/src/com/android/tv/common/TvContentRatingCache.java
@@ -20,6 +20,7 @@ import android.media.tv.TvContentRating;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import java.util.ArrayList;
@@ -42,8 +43,7 @@ public final class TvContentRatingCache implements MemoryManageable {
return INSTANCE;
}
- private final Map<String, TvContentRating[]> mRatingsMultiMap = CollectionUtils
- .createSmallMap();
+ private final Map<String, TvContentRating[]> mRatingsMultiMap = new ArrayMap<>();
/**
* Returns an array TvContentRatings from a string of comma separated set of rating strings
diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java
index bfef19a6..9925833f 100644
--- a/common/src/com/android/tv/common/feature/CommonFeatures.java
+++ b/common/src/com/android/tv/common/feature/CommonFeatures.java
@@ -17,7 +17,7 @@
package com.android.tv.common.feature;
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.OR;
import static com.android.tv.common.feature.TestableFeature.createTestableFeature;
/**
@@ -32,9 +32,12 @@ public class CommonFeatures {
* <p>See <a href="https://goto.google.com/atv-dvr-onepager">go/atv-dvr-onepager</a>
*/
public static TestableFeature DVR = createTestableFeature(
- AND(
- ENG_ONLY_FEATURE,
- new PropertyFeature("dvr_enabled", false),
- Sdk.M_FEATURE // TODO(dvr): Sdk.N_PREVIEW_FEATURE
- ));
+ OR(ENG_ONLY_FEATURE, Sdk.N_PRE_2_OR_HIGHER));
+
+ /**
+ * USE_SW_CODEC_FOR_SD
+ *
+ * Prefer software based codec for SD channels.
+ */
+ public static Feature USE_SW_CODEC_FOR_SD = new PropertyFeature("use_sw_codec_for_sd", true);
}
diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java
index 1efefd89..268eaea7 100644
--- a/common/src/com/android/tv/common/feature/Sdk.java
+++ b/common/src/com/android/tv/common/feature/Sdk.java
@@ -18,25 +18,50 @@ package com.android.tv.common.feature;
import android.content.Context;
import android.os.Build;
+import android.support.v4.os.BuildCompat;
/**
* Holder for SDK version features
*/
public class Sdk {
- public static Feature M_FEATURE = new SdkVersionFeature(Build.VERSION_CODES.M);
- private static class SdkVersionFeature implements Feature {
+ public static Feature N_PRE_2_OR_HIGHER =
+ new SdkPreviewVersionFeature(Build.VERSION_CODES.M, 2, true);
+
+ private static class SdkPreviewVersionFeature implements Feature {
private final int mVersionCode;
+ private final int mPreviewCode;
+ private final boolean mAllowHigherPreview;
- private SdkVersionFeature(int versionCode) {
+ private SdkPreviewVersionFeature(int versionCode, int previewCode,
+ boolean allowHigerPreview) {
mVersionCode = versionCode;
+ mPreviewCode = previewCode;
+ mAllowHigherPreview = allowHigerPreview;
}
@Override
public boolean isEnabled(Context context) {
- return Build.VERSION.SDK_INT >= mVersionCode;
+ try {
+ if (mAllowHigherPreview) {
+ return Build.VERSION.SDK_INT == mVersionCode
+ && Build.VERSION.PREVIEW_SDK_INT >= mPreviewCode;
+ } else {
+ return Build.VERSION.SDK_INT == mVersionCode
+ && Build.VERSION.PREVIEW_SDK_INT == mPreviewCode;
+ }
+ } catch (NoSuchFieldError e) {
+ return false;
+ }
}
}
+ public static Feature AT_LEAST_N = new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ return BuildCompat.isAtLeastN();
+ }
+ };
+
private Sdk() {}
}
diff --git a/common/src/com/android/tv/common/recording/PlaybackTvView.java b/common/src/com/android/tv/common/recording/PlaybackTvView.java
deleted file mode 100644
index e62ee06f..00000000
--- a/common/src/com/android/tv/common/recording/PlaybackTvView.java
+++ /dev/null
@@ -1,191 +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.common.recording;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract;
-import android.media.tv.TvTrackInfo;
-import android.media.tv.TvView;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.AttributeSet;
-
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.util.List;
-
-/**
- * Extend {@link TvView} to support recording playback.
- */
-@TargetApi(Build.VERSION_CODES.M) // TODO(DVR): set to N
-public class PlaybackTvView extends TvView {
-
- final TvInputCallback mInternalCallback = new TvInputCallback() {
- @Override
- public void onChannelRetuned(String inputId, Uri channelUri) {
- if (mCallback != null) {
- mCallback.onChannelRetuned(inputId, channelUri);
- }
- }
-
- @Override
- public void onConnectionFailed(String inputId) {
- if (mCallback != null) {
- mCallback.onConnectionFailed(inputId);
- }
- }
-
- @Override
- public void onContentAllowed(String inputId) {
- if (mCallback != null) {
- mCallback.onContentAllowed(inputId);
- }
- }
-
- @Override
- public void onContentBlocked(String inputId, TvContentRating rating) {
- if (mCallback != null) {
- mCallback.onContentBlocked(inputId, rating);
- }
- }
-
- @Override
- public void onDisconnected(String inputId) {
- if (mCallback != null) {
- mCallback.onDisconnected(inputId);
- }
- }
-
- @Override
- public void onEvent(String inputId, String eventType, Bundle eventArgs) {
- if (mCallback != null) {
- if (eventType.equals(RecordingUtils.EVENT_TYPE_TIMESHIFT_END_POSITION)) {
- if (mTimeshiftCallback != null) {
- mTimeshiftCallback.onTimeShiftEndPositionChanged(inputId,
- eventArgs.getLong(RecordingUtils.BUNDLE_TIMESHIFT_END_POSITION));
- }
- return;
- }
- mCallback.onEvent(inputId, eventType, eventArgs);
- }
- }
-
- @Override
- public void onTimeShiftStatusChanged(String inputId, int status) {
- if (mCallback != null) {
- mCallback.onTimeShiftStatusChanged(inputId, status);
- }
- }
-
- @Override
- public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
- if (mCallback != null) {
- mCallback.onTracksChanged(inputId, tracks);
- }
- }
-
- @Override
- public void onTrackSelected(String inputId, int type, String trackId) {
- if (mCallback != null) {
- mCallback.onTrackSelected(inputId, type, trackId);
- }
- }
-
- @Override
- public void onVideoAvailable(String inputId) {
- if (mCallback != null) {
- mCallback.onVideoAvailable(inputId);
- }
- }
-
- @Override
- public void onVideoSizeChanged(String inputId, int width, int height) {
- if (mCallback != null) {
- mCallback.onVideoSizeChanged(inputId, width, height);
- }
- }
-
- @Override
- public void onVideoUnavailable(String inputId, int reason) {
- if (mCallback != null) {
- mCallback.onVideoUnavailable(inputId, reason);
- }
- }
- };
-
- private TvInputCallback mCallback;
- private TimeShiftPositionCallback2 mTimeshiftCallback;
-
- public PlaybackTvView(Context context) {
- this(context, null, 0);
- }
-
- public PlaybackTvView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PlaybackTvView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- /**
- * Start playback of recording. Once TvInput is ready to play, onVideoAvailable will be called.
- * Playback control will be done with timeshift method for seek, play, pause.
- */
- public void playMedia(String inputId, Uri mediaUri) {
- tune(inputId, TvContract.buildChannelUri(0), RecordingUtils.buildMediaUri(mediaUri));
- }
-
- @Override
- public void tune(String inputId, Uri channelUri, Bundle params) {
- super.tune(inputId, channelUri, params);
- if (CommonFeatures.DVR.isEnabled(getContext())) {
- sendAppPrivateCommand(RecordingUtils.APP_PRIV_CREATE_PLAYBACK_SESSION, null);
- }
- }
-
- public void setTimeShiftPositionCallback(TimeShiftPositionCallback2 callback) {
- if (CommonFeatures.DVR.isEnabled(getContext())) {
- mTimeshiftCallback = callback;
- }
- super.setTimeShiftPositionCallback(callback);
- }
-
- @Override
- public void setCallback(TvInputCallback callback) {
- if (CommonFeatures.DVR.isEnabled(getContext())) {
- mCallback = callback;
- if (callback == null) {
- super.setCallback(null);
- } else {
- super.setCallback(mInternalCallback);
- }
- } else {
- super.setCallback(callback);
- }
- }
-
- /**
- * We need end position for recording playback.
- */
- public abstract static class TimeShiftPositionCallback2 extends TimeShiftPositionCallback {
- public void onTimeShiftEndPositionChanged(String inputId, long timeMs) { }
- }
-}
diff --git a/common/src/com/android/tv/common/recording/RecordedProgram.java b/common/src/com/android/tv/common/recording/RecordedProgram.java
new file mode 100644
index 00000000..63ce6ff9
--- /dev/null
+++ b/common/src/com/android/tv/common/recording/RecordedProgram.java
@@ -0,0 +1,760 @@
+/*
+ * 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.common.recording;
+
+import static android.media.tv.TvContract.RecordedPrograms;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.tv.common.R;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}.
+ */
+public class RecordedProgram {
+ public static final int ID_NOT_SET = -1;
+
+ public final static String[] PROJECTION = {
+ // These are in exactly the order listed in RecordedPrograms
+ RecordedPrograms._ID,
+ RecordedPrograms.COLUMN_INPUT_ID,
+ RecordedPrograms.COLUMN_CHANNEL_ID,
+ RecordedPrograms.COLUMN_TITLE,
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_SEASON_TITLE,
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_EPISODE_TITLE,
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
+ RecordedPrograms.COLUMN_LONG_DESCRIPTION,
+ RecordedPrograms.COLUMN_VIDEO_WIDTH,
+ RecordedPrograms.COLUMN_VIDEO_HEIGHT,
+ RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
+ RecordedPrograms.COLUMN_CONTENT_RATING,
+ RecordedPrograms.COLUMN_POSTER_ART_URI,
+ RecordedPrograms.COLUMN_THUMBNAIL_URI,
+ RecordedPrograms.COLUMN_SEARCHABLE,
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ RecordedPrograms.COLUMN_VERSION_NUMBER,
+ };
+
+ public static final RecordedProgram fromCursor(Cursor cursor) {
+ int index = 0;
+ return builder()
+ .setId(cursor.getLong(index++))
+ .setInputId(cursor.getString(index++))
+ .setChannelId(cursor.getLong(index++))
+ .setTitle(cursor.getString(index++))
+ .setSeasonNumber(cursor.getString(index++))
+ .setSeasonTitle(cursor.getString(index++))
+ .setEpisodeNumber(cursor.getString(index++))
+ .setEpisodeTitle(cursor.getString(index++))
+ .setStartTimeUtcMillis(cursor.getLong(index++))
+ .setEndTimeUtcMillis(cursor.getLong(index++))
+ .setBroadcastGenres(cursor.getString(index++))
+ .setCanonicalGenres(cursor.getString(index++))
+ .setShortDescription(cursor.getString(index++))
+ .setLongDescription(cursor.getString(index++))
+ .setVideoWidth(cursor.getInt(index++))
+ .setVideoHeight(cursor.getInt(index++))
+ .setAudioLanguage(cursor.getString(index++))
+ .setContentRating(cursor.getString(index++))
+ .setPosterArt(cursor.getString(index++))
+ .setThumbnail(cursor.getString(index++))
+ .setSearchable(cursor.getInt(index++) == 1)
+ .setDataUri(cursor.getString(index++))
+ .setDataBytes(cursor.getLong(index++))
+ .setDurationMillis(cursor.getLong(index++))
+ .setExpireTimeUtcMillis(cursor.getLong(index++))
+ .setInternalProviderData(cursor.getBlob(index++))
+ .setInternalProviderFlag1(cursor.getInt(index++))
+ .setInternalProviderFlag2(cursor.getInt(index++))
+ .setInternalProviderFlag3(cursor.getInt(index++))
+ .setInternalProviderFlag4(cursor.getInt(index++))
+ .setVersionNumber(cursor.getInt(index++))
+ .build();
+ }
+
+ public static ContentValues toValues(RecordedProgram recordedProgram) {
+ ContentValues values = new ContentValues();
+ if (recordedProgram.mId != ID_NOT_SET) {
+ values.put(RecordedPrograms._ID, recordedProgram.mId);
+ }
+ values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId);
+ values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId);
+ values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle);
+ values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber);
+ values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle);
+ values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber);
+ values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle);
+ values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ recordedProgram.mStartTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ safeEncode(recordedProgram.mBroadcastGenres));
+ values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ safeEncode(recordedProgram.mCanonicalGenres));
+ values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription);
+ values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription);
+ if (recordedProgram.mVideoWidth == 0) {
+ values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH);
+ } else {
+ values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth);
+ }
+ if (recordedProgram.mVideoHeight == 0) {
+ values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT);
+ } else {
+ values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight);
+ }
+ values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage);
+ values.put(RecordedPrograms.COLUMN_CONTENT_RATING, recordedProgram.mContentRating);
+ values.put(RecordedPrograms.COLUMN_POSTER_ART_URI,
+ safeToString(recordedProgram.mPosterArt));
+ values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, safeToString(recordedProgram.mThumbnail));
+ values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ safeToString(recordedProgram.mDataUri));
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ recordedProgram.mDurationMillis);
+ values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ recordedProgram.mExpireTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ recordedProgram.mInternalProviderData);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ recordedProgram.mInternalProviderFlag1);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ recordedProgram.mInternalProviderFlag2);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ recordedProgram.mInternalProviderFlag3);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ recordedProgram.mInternalProviderFlag4);
+ values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber);
+ return values;
+ }
+
+ public static class Builder{
+ private long mId = ID_NOT_SET;
+ private String mInputId;
+ private long mChannelId;
+ private String mTitle;
+ private String mSeasonNumber;
+ private String mSeasonTitle;
+ private String mEpisodeNumber;
+ private String mEpisodeTitle;
+ private long mStartTimeUtcMillis;
+ private long mEndTimeUtcMillis;
+ private String[] mBroadcastGenres;
+ private String[] mCanonicalGenres;
+ private String mShortDescription;
+ private String mLongDescription;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private String mAudioLanguage;
+ private String mContentRating;
+ private Uri mPosterArt;
+ private Uri mThumbnail;
+ private boolean mSearchable = true;
+ private Uri mDataUri;
+ private long mDataBytes;
+ private long mDurationMillis;
+ private long mExpireTimeUtcMillis;
+ private byte[] mInternalProviderData;
+ private int mInternalProviderFlag1;
+ private int mInternalProviderFlag2;
+ private int mInternalProviderFlag3;
+ private int mInternalProviderFlag4;
+ private int mVersionNumber;
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setInputId(String inputId) {
+ mInputId = inputId;
+ return this;
+ }
+
+ public Builder setChannelId(long channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setSeasonNumber(String seasonNumber) {
+ mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ public Builder setSeasonTitle(String seasonTitle) {
+ mSeasonTitle = seasonTitle;
+ return this;
+ }
+
+ public Builder setEpisodeNumber(String episodeNumber) {
+ mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
+ public Builder setEpisodeTitle(String episodeTitle) {
+ mEpisodeTitle = episodeTitle;
+ return this;
+ }
+
+ public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
+ mStartTimeUtcMillis = startTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
+ mEndTimeUtcMillis = endTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setBroadcastGenres(String broadcastGenres) {
+ if (TextUtils.isEmpty(broadcastGenres)) {
+ mBroadcastGenres = null;
+ return this;
+ }
+ return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres));
+ }
+
+ private Builder setBroadcastGenres(String[] broadcastGenres) {
+ mBroadcastGenres = broadcastGenres;
+ return this;
+ }
+
+ public Builder setCanonicalGenres(String canonicalGenres) {
+ if (TextUtils.isEmpty(canonicalGenres)) {
+ mCanonicalGenres = null;
+ return this;
+ }
+ return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres));
+ }
+
+ private Builder setCanonicalGenres(String[] canonicalGenres) {
+ mCanonicalGenres = canonicalGenres;
+ return this;
+ }
+
+ public Builder setShortDescription(String shortDescription) {
+ mShortDescription = shortDescription;
+ return this;
+ }
+
+ public Builder setLongDescription(String longDescription) {
+ mLongDescription = longDescription;
+ return this;
+ }
+
+ public Builder setVideoWidth(int videoWidth) {
+ mVideoWidth = videoWidth;
+ return this;
+ }
+
+ public Builder setVideoHeight(int videoHeight) {
+ mVideoHeight = videoHeight;
+ return this;
+ }
+
+ public Builder setAudioLanguage(String audioLanguage) {
+ mAudioLanguage = audioLanguage;
+ return this;
+ }
+
+ public Builder setContentRating(String contentRating) {
+ mContentRating = contentRating;
+ return this;
+ }
+
+ private Uri toUri(String uriString) {
+ try {
+ return uriString == null ? null : Uri.parse(uriString);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public Builder setPosterArt(String posterArtUri) {
+ return setPosterArt(toUri(posterArtUri));
+ }
+
+ public Builder setPosterArt(Uri posterArt) {
+ mPosterArt = posterArt;
+ return this;
+ }
+
+ public Builder setThumbnail(String thumbnailUri) {
+ return setThumbnail(toUri(thumbnailUri));
+ }
+
+ public Builder setThumbnail(Uri thumbnail) {
+ mThumbnail = thumbnail;
+ return this;
+ }
+
+ public Builder setSearchable(boolean searchable) {
+ mSearchable = searchable;
+ return this;
+ }
+
+ public Builder setDataUri(String dataUri) {
+ return setDataUri(toUri(dataUri));
+ }
+
+ public Builder setDataUri(Uri dataUri) {
+ mDataUri = dataUri;
+ return this;
+ }
+
+ public Builder setDataBytes(long dataBytes) {
+ mDataBytes = dataBytes;
+ return this;
+ }
+
+ public Builder setDurationMillis(long durationMillis) {
+ mDurationMillis = durationMillis;
+ return this;
+ }
+
+ public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) {
+ mExpireTimeUtcMillis = expireTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setInternalProviderData(byte[] internalProviderData) {
+ mInternalProviderData = internalProviderData;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag1(int internalProviderFlag1) {
+ mInternalProviderFlag1 = internalProviderFlag1;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag2(int internalProviderFlag2) {
+ mInternalProviderFlag2 = internalProviderFlag2;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag3(int internalProviderFlag3) {
+ mInternalProviderFlag3 = internalProviderFlag3;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag4(int internalProviderFlag4) {
+ mInternalProviderFlag4 = internalProviderFlag4;
+ return this;
+ }
+
+ public Builder setVersionNumber(int versionNumber) {
+ mVersionNumber = versionNumber;
+ return this;
+ }
+
+ public RecordedProgram build() {
+ return new RecordedProgram(mId, mInputId, mChannelId, mTitle, mSeasonNumber,
+ mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis,
+ mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription,
+ mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRating,
+ mPosterArt, mThumbnail, mSearchable, mDataUri, mDataBytes, mDurationMillis,
+ mExpireTimeUtcMillis, mInternalProviderData, mInternalProviderFlag1,
+ mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4,
+ mVersionNumber);
+ }
+ }
+
+ public static Builder builder() { return new Builder(); }
+
+ public static Builder buildFrom(RecordedProgram orig) {
+ return builder()
+ .setId(orig.getId())
+ .setInputId(orig.getInputId())
+ .setChannelId(orig.getChannelId())
+ .setTitle(orig.getTitle())
+ .setSeasonNumber(orig.getSeasonNumber())
+ .setSeasonTitle(orig.getSeasonTitle())
+ .setEpisodeNumber(orig.getEpisodeNumber())
+ .setEpisodeTitle(orig.getEpisodeTitle())
+ .setStartTimeUtcMillis(orig.getStartTimeUtcMillis())
+ .setEndTimeUtcMillis(orig.getEndTimeUtcMillis())
+ .setBroadcastGenres(orig.getBroadcastGenres())
+ .setCanonicalGenres(orig.getCanonicalGenres())
+ .setShortDescription(orig.getShortDescription())
+ .setLongDescription(orig.getLongDescription())
+ .setVideoWidth(orig.getVideoWidth())
+ .setVideoHeight(orig.getVideoHeight())
+ .setAudioLanguage(orig.getAudioLanguage())
+ .setContentRating(orig.getContentRating())
+ .setPosterArt(orig.getPosterArt())
+ .setThumbnail(orig.getThumbnail())
+ .setSearchable(orig.isSearchable())
+ .setInternalProviderData(orig.getInternalProviderData())
+ .setInternalProviderFlag1(orig.getInternalProviderFlag1())
+ .setInternalProviderFlag2(orig.getInternalProviderFlag2())
+ .setInternalProviderFlag3(orig.getInternalProviderFlag3())
+ .setInternalProviderFlag4(orig.getInternalProviderFlag4())
+ .setVersionNumber(orig.getVersionNumber());
+ }
+
+ public static final Comparator<RecordedProgram> START_TIME_THEN_ID_COMPARATOR
+ = new Comparator<RecordedProgram>() {
+ @Override
+ public int compare(RecordedProgram lhs, RecordedProgram rhs) {
+ int res = Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis());
+ if (res != 0) {
+ return res;
+ }
+ return Long.compare(lhs.mId, rhs.mId);
+ }
+ };
+
+ private final long mId;
+ private final String mInputId;
+ private final long mChannelId;
+ private final String mTitle;
+ private final String mSeasonNumber;
+ private final String mSeasonTitle;
+ private final String mEpisodeNumber;
+ private final String mEpisodeTitle;
+ private final long mStartTimeUtcMillis;
+ private final long mEndTimeUtcMillis;
+ private final String[] mBroadcastGenres;
+ private final String[] mCanonicalGenres;
+ private final String mShortDescription;
+ private final String mLongDescription;
+ private final int mVideoWidth;
+ private final int mVideoHeight;
+ private final String mAudioLanguage;
+ private final String mContentRating;
+ private final Uri mPosterArt;
+ private final Uri mThumbnail;
+ private final boolean mSearchable;
+ private final Uri mDataUri;
+ private final long mDataBytes;
+ private final long mDurationMillis;
+ private final long mExpireTimeUtcMillis;
+ private final byte[] mInternalProviderData;
+ private final int mInternalProviderFlag1;
+ private final int mInternalProviderFlag2;
+ private final int mInternalProviderFlag3;
+ private final int mInternalProviderFlag4;
+ private final int mVersionNumber;
+
+ private RecordedProgram(long id, String inputId, long channelId, String title,
+ String seasonNumber, String seasonTitle, String episodeNumber, String episodeTitle,
+ long startTimeUtcMillis, long endTimeUtcMillis, String[] broadcastGenres,
+ String[] canonicalGenres, String shortDescription, String longDescription,
+ int videoWidth, int videoHeight, String audioLanguage, String contentRating,
+ Uri posterArt, Uri thumbnail, boolean searchable, Uri dataUri, long dataBytes,
+ long durationMillis, long expireTimeUtcMillis, byte[] internalProviderData,
+ int internalProviderFlag1, int internalProviderFlag2, int internalProviderFlag3,
+ int internalProviderFlag4, int versionNumber) {
+ mId = id;
+ mInputId = inputId;
+ mChannelId = channelId;
+ mTitle = title;
+ mSeasonNumber = seasonNumber;
+ mSeasonTitle = seasonTitle;
+ mEpisodeNumber = episodeNumber;
+ mEpisodeTitle = episodeTitle;
+ mStartTimeUtcMillis = startTimeUtcMillis;
+ mEndTimeUtcMillis = endTimeUtcMillis;
+ mBroadcastGenres = broadcastGenres;
+ mCanonicalGenres = canonicalGenres;
+ mShortDescription = shortDescription;
+ mLongDescription = longDescription;
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
+
+ mAudioLanguage = audioLanguage;
+ mContentRating = contentRating;
+ mPosterArt = posterArt;
+ mThumbnail = thumbnail;
+ mSearchable = searchable;
+ mDataUri = dataUri;
+ mDataBytes = dataBytes;
+ mDurationMillis = durationMillis;
+ mExpireTimeUtcMillis = expireTimeUtcMillis;
+ mInternalProviderData = internalProviderData;
+ mInternalProviderFlag1 = internalProviderFlag1;
+ mInternalProviderFlag2 = internalProviderFlag2;
+ mInternalProviderFlag3 = internalProviderFlag3;
+ mInternalProviderFlag4 = internalProviderFlag4;
+ mVersionNumber = versionNumber;
+ }
+
+ public String getAudioLanguage() {
+ return mAudioLanguage;
+ }
+
+ public String[] getBroadcastGenres() {
+ return mBroadcastGenres;
+ }
+
+ public String[] getCanonicalGenres() {
+ return mCanonicalGenres;
+ }
+
+ public long getChannelId() {
+ return mChannelId;
+ }
+
+ public String getContentRating() {
+ return mContentRating;
+ }
+
+ public Uri getDataUri() {
+ return mDataUri;
+ }
+
+ public long getDataBytes() {
+ return mDataBytes;
+ }
+
+ public long getDurationMillis() {
+ return mDurationMillis;
+ }
+
+ public long getEndTimeUtcMillis() {
+ return mEndTimeUtcMillis;
+ }
+
+ public String getEpisodeNumber() {
+ return mEpisodeNumber;
+ }
+
+ public String getEpisodeTitle() {
+ return mEpisodeTitle;
+ }
+
+ public String getEpisodeDisplayTitle(Context context) {
+ if (!TextUtils.isEmpty(mSeasonNumber) && !TextUtils.isEmpty(mEpisodeNumber)
+ && !TextUtils.isEmpty(mEpisodeTitle)) {
+ return String.format(context.getResources().getString(R.string.episode_format),
+ mSeasonNumber, mEpisodeNumber, mEpisodeTitle);
+ }
+ return mEpisodeTitle;
+ }
+
+ public long getExpireTimeUtcMillis() {
+ return mExpireTimeUtcMillis;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public String getInputId() {
+ return mInputId;
+ }
+
+ public byte[] getInternalProviderData() {
+ return mInternalProviderData;
+ }
+
+ public int getInternalProviderFlag1() {
+ return mInternalProviderFlag1;
+ }
+
+ public int getInternalProviderFlag2() {
+ return mInternalProviderFlag2;
+ }
+
+ public int getInternalProviderFlag3() {
+ return mInternalProviderFlag3;
+ }
+
+ public int getInternalProviderFlag4() {
+ return mInternalProviderFlag4;
+ }
+
+ public String getLongDescription() {
+ return mLongDescription;
+ }
+
+ public Uri getPosterArt() {
+ return mPosterArt;
+ }
+
+ public boolean isSearchable() {
+ return mSearchable;
+ }
+
+ public String getSeasonNumber() {
+ return mSeasonNumber;
+ }
+
+ public String getSeasonTitle() {
+ return mSeasonTitle;
+ }
+
+ public String getShortDescription() {
+ return mShortDescription;
+ }
+
+ public long getStartTimeUtcMillis() {
+ return mStartTimeUtcMillis;
+ }
+
+ public Uri getThumbnail() {
+ return mThumbnail;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public Uri getUri() {
+ return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId);
+ }
+
+ public int getVersionNumber() {
+ return mVersionNumber;
+ }
+
+ public int getVideoHeight() {
+ return mVideoHeight;
+ }
+
+ public int getVideoWidth() {
+ return mVideoWidth;
+ }
+
+ /**
+ * Compares everything except {@link #getInternalProviderData()}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RecordedProgram that = (RecordedProgram) o;
+ return Objects.equals(mId, that.mId) &&
+ Objects.equals(mChannelId, that.mChannelId) &&
+ Objects.equals(mSeasonNumber, that.mSeasonNumber) &&
+ Objects.equals(mSeasonTitle, that.mSeasonTitle) &&
+ Objects.equals(mEpisodeNumber, that.mEpisodeNumber) &&
+ Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) &&
+ Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) &&
+ Objects.equals(mVideoWidth, that.mVideoWidth) &&
+ Objects.equals(mVideoHeight, that.mVideoHeight) &&
+ Objects.equals(mSearchable, that.mSearchable) &&
+ Objects.equals(mDataBytes, that.mDataBytes) &&
+ Objects.equals(mDurationMillis, that.mDurationMillis) &&
+ Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) &&
+ Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) &&
+ Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) &&
+ Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) &&
+ Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) &&
+ Objects.equals(mVersionNumber, that.mVersionNumber) &&
+ Objects.equals(mTitle, that.mTitle) &&
+ Objects.equals(mEpisodeTitle, that.mEpisodeTitle) &&
+ Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) &&
+ Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) &&
+ Objects.equals(mShortDescription, that.mShortDescription) &&
+ Objects.equals(mLongDescription, that.mLongDescription) &&
+ Objects.equals(mAudioLanguage, that.mAudioLanguage) &&
+ Objects.equals(mContentRating, that.mContentRating) &&
+ Objects.equals(mPosterArt, that.mPosterArt) &&
+ Objects.equals(mThumbnail, that.mThumbnail);
+ }
+
+ /**
+ * Hashes based on the ID.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "RecordedProgram"
+ + "[" + mId +
+ "]{ mInputId=" + mInputId +
+ ", mChannelId='" + mChannelId + '\'' +
+ ", mTitle='" + mTitle + '\'' +
+ ", mEpisodeNumber=" + mEpisodeNumber +
+ ", mEpisodeTitle='" + mEpisodeTitle + '\'' +
+ ", mStartTimeUtcMillis=" + mStartTimeUtcMillis +
+ ", mEndTimeUtcMillis=" + mEndTimeUtcMillis +
+ ", mBroadcastGenres=" +
+ (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") +
+ ", mCanonicalGenres=" +
+ (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") +
+ ", mShortDescription='" + mShortDescription + '\'' +
+ ", mLongDescription='" + mLongDescription + '\'' +
+ ", mVideoHeight=" + mVideoHeight +
+ ", mVideoWidth=" + mVideoWidth +
+ ", mAudioLanguage='" + mAudioLanguage + '\'' +
+ ", mContentRating='" + mContentRating + '\'' +
+ ", mPosterArt=" + mPosterArt +
+ ", mThumbnail=" + mThumbnail +
+ ", mSearchable=" + mSearchable +
+ ", mDataUri=" + mDataUri +
+ ", mDataBytes=" + mDataBytes +
+ ", mDurationMillis=" + mDurationMillis +
+ ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis +
+ ", mInternalProviderData.length=" +
+ (mInternalProviderData == null ? "null" : mInternalProviderData.length) +
+ ", mInternalProviderFlag1=" + mInternalProviderFlag1 +
+ ", mInternalProviderFlag2=" + mInternalProviderFlag2 +
+ ", mInternalProviderFlag3=" + mInternalProviderFlag3 +
+ ", mInternalProviderFlag4=" + mInternalProviderFlag4 +
+ ", mSeasonNumber=" + mSeasonNumber +
+ ", mSeasonTitle=" + mSeasonTitle +
+ ", mVersionNumber=" + mVersionNumber +
+ '}';
+ }
+
+ @Nullable
+ private static String safeToString(@Nullable Object o) {
+ return o == null ? null : o.toString();
+ }
+
+ @Nullable
+ private static String safeEncode(@Nullable String[] genres) {
+ return genres == null ? null : TvContract.Programs.Genres.encode(genres);
+ }
+}
diff --git a/common/src/com/android/tv/common/recording/RecordingTvInputService.java b/common/src/com/android/tv/common/recording/RecordingTvInputService.java
deleted file mode 100644
index 6eea6ae7..00000000
--- a/common/src/com/android/tv/common/recording/RecordingTvInputService.java
+++ /dev/null
@@ -1,378 +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.common.recording;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.media.PlaybackParams;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvInputService;
-import android.media.tv.TvTrackInfo;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.MotionEvent;
-import android.view.Surface;
-
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.util.List;
-
-/**
- * {@link TvInputService} class that supports recording and playback.
- */
-@TargetApi(Build.VERSION_CODES.M) // TODO(DVR): set to N
-public abstract class RecordingTvInputService extends TvInputService {
- private static final String TAG = "DvrTvInputService";
- private static final boolean DEBUG = true;
-
- @Override
- public final Session onCreateSession(String inputId) {
- if (CommonFeatures.DVR.isEnabled(this)) {
- return new InternalSession(this, inputId);
- } else {
- return onCreatePlaybackSession(inputId);
- }
- }
-
- /**
- * Called when {@link com.android.tv.common.recording.DvrSession#connect} is called.
- */
- protected TvRecording.RecordingSession onCreateDvrSession(String inputId) {
- return null;
- }
-
- protected abstract PlaybackSession onCreatePlaybackSession(String inputId);
-
- private class InternalSession extends TvInputService.Session {
- final String mInputId;
- BaseSession mSessionImpl;
-
- public InternalSession(Context context, String inputId) {
- super(context);
- mInputId = inputId;
- }
-
- @Override
- public void onRelease() {
- if (mSessionImpl != null) {
- mSessionImpl.onRelease();
- }
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- return mSessionImpl.onSetSurface(surface);
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- mSessionImpl.onSetStreamVolume(volume);
- }
-
- @Override
- public boolean onTune(Uri channelUri) {
- return mSessionImpl.onTune(channelUri);
- }
-
- @Override
- public void onAppPrivateCommand(String action, Bundle data) {
- if (action.equals(RecordingUtils.APP_PRIV_CREATE_DVR_SESSION)) {
- if (mSessionImpl == null) {
- mSessionImpl = onCreateDvrSession(mInputId);
- if (mSessionImpl != null) {
- mSessionImpl.setPassthroughSession(this);
- notifySessionEvent(RecordingUtils.EVENT_TYPE_CONNECTED, null);
- }
- }
- } else if (action.equals(RecordingUtils.APP_PRIV_CREATE_PLAYBACK_SESSION)) {
- if (mSessionImpl == null) {
- mSessionImpl = onCreatePlaybackSession(mInputId);
- if (mSessionImpl != null) {
- mSessionImpl.setPassthroughSession(this);
- }
- }
- } else {
- if (mSessionImpl == null) {
- throw new IllegalStateException();
- }
- mSessionImpl.onAppPrivateCommand(action, data);
- }
- }
-
- @Override
- public android.view.View onCreateOverlayView() {
- return mSessionImpl.onCreateOverlayView();
- }
-
- @Override
- public boolean onGenericMotionEvent(android.view.MotionEvent event) {
- return mSessionImpl.onGenericMotionEvent(event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
- return mSessionImpl.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyLongPress(int keyCode, android.view.KeyEvent event) {
- return mSessionImpl.onKeyLongPress(keyCode, event);
- }
-
- @Override
- public boolean onKeyMultiple(int keyCode, int count, android.view.KeyEvent event) {
- return mSessionImpl.onKeyMultiple(keyCode, count, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, android.view.KeyEvent event) {
- return mSessionImpl.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onOverlayViewSizeChanged(int width, int height) {
- mSessionImpl.onOverlayViewSizeChanged(width, height);
- }
-
- @Override
- public boolean onSelectTrack(int type, String trackId) {
- return mSessionImpl.onSelectTrack(type, trackId);
- }
-
- @Override
- public void onSetMain(boolean isMain) {
- mSessionImpl.onSetMain(isMain);
- }
-
- @Override
- public void onSurfaceChanged(int format, int width, int height) {
- mSessionImpl.onSurfaceChanged(format, width, height);
- }
-
- @Override
- public long onTimeShiftGetCurrentPosition() {
- return mSessionImpl.onTimeShiftGetCurrentPosition();
- }
-
- @Override
- public long onTimeShiftGetStartPosition() {
- return mSessionImpl.onTimeShiftGetStartPosition();
- }
-
- @Override
- public void onTimeShiftPause() {
- mSessionImpl.onTimeShiftPause();
- }
-
- @Override
- public void onTimeShiftResume() {
- mSessionImpl.onTimeShiftResume();
- }
-
- @Override
- public void onTimeShiftSeekTo(long timeMs) {
- mSessionImpl.onTimeShiftSeekTo(timeMs);
- }
-
- @Override
- public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
- mSessionImpl.onTimeShiftSetPlaybackParams(params);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return mSessionImpl.onTouchEvent(event);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- return mSessionImpl.onTrackballEvent(event);
- }
-
- @Override
- public boolean onTune(Uri channelUri, Bundle params) {
- return mSessionImpl.onTune(channelUri, params);
- }
-
- @Override
- public void onUnblockContent(TvContentRating unblockedRating) {
- mSessionImpl.onUnblockContent(unblockedRating);
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- mSessionImpl.onSetCaptionEnabled(enabled);
- }
- }
-
- /**
- * Base class for {@link PlaybackSession} and {@link TvRecording.RecordingSession}. Do not use it directly
- * outside of this class.
- */
- public static abstract class BaseSession extends TvInputService.Session {
- private Session mPassthroughSession;
-
- public BaseSession(Context context) {
- super(context);
- }
-
- private void setPassthroughSession(Session passthroughSession) {
- mPassthroughSession = passthroughSession;
- }
-
- @Override
- public void setOverlayViewEnabled(boolean enable) {
- if (mPassthroughSession != null) {
- mPassthroughSession.setOverlayViewEnabled(enable);
- } else {
- super.setOverlayViewEnabled(enable);
- }
- }
-
- @Override
- public void notifyChannelRetuned(Uri channelUri) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyChannelRetuned(channelUri);
- } else {
- super.notifyChannelRetuned(channelUri);
- }
- }
-
- @Override
- public void notifyContentAllowed() {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyContentAllowed();
- } else {
- super.notifyContentAllowed();
- }
- }
-
- @Override
- public void notifyContentBlocked(TvContentRating rating) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyContentBlocked(rating);
- } else {
- super.notifyContentBlocked(rating);
- }
- }
-
- @Override
- public void notifySessionEvent(String eventType, Bundle eventArgs) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifySessionEvent(eventType, eventArgs);
- } else {
- super.notifySessionEvent(eventType, eventArgs);
- }
- }
-
- @Override
- public void notifyTimeShiftStatusChanged(int status) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyTimeShiftStatusChanged(status);
- } else {
- super.notifyTimeShiftStatusChanged(status);
- }
- }
-
- @Override
- public void notifyTracksChanged(List<TvTrackInfo> tracks) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyTracksChanged(tracks);
- } else {
- super.notifyTracksChanged(tracks);
- }
- }
-
- @Override
- public void notifyTrackSelected(int type, String trackId) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyTrackSelected(type, trackId);
- } else {
- super.notifyTrackSelected(type, trackId);
- }
- }
-
- @Override
- public void notifyVideoAvailable() {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyVideoAvailable();
- } else {
- super.notifyVideoAvailable();
- }
- }
-
- @Override
- public void notifyVideoUnavailable(int reason) {
- if (mPassthroughSession != null) {
- mPassthroughSession.notifyVideoUnavailable(reason);
- } else {
- super.notifyVideoUnavailable(reason);
- }
- }
- }
-
- /**
- * Session linked to {@link android.media.tv.TvView} to tune to a channel or play an recording.
- */
- public static abstract class PlaybackSession extends BaseSession {
- private boolean mIsRecordingPlayback;
-
- public PlaybackSession(Context context) {
- super(context);
- }
-
- /**
- * Returns {@code true}, if the current playback is for a recording.
- */
- public boolean isRecordingPlayback() {
- return mIsRecordingPlayback;
- }
-
- /**
- * Called when it is requested to play an recording {@code mediaUri}. When playback and
- * rendering starts, {@link #notifyVideoAvailable} should be called.
- */
- public void onPlayMedia(Uri mediaUri) { }
-
- /**
- * Notifies TimeShift end position. It should have the form like onTimeShiftEndPosition.
- * But, it's not trivial to add that in the prototyping. The method is recommended to be
- * called inside {@link #onTimeShiftGetStartPosition()}, when a recording is played.
- */
- public void notifyTimeShiftEndPosition(long endPosition) {
- Bundle params = new Bundle();
- params.putLong(RecordingUtils.BUNDLE_TIMESHIFT_END_POSITION, endPosition);
- notifySessionEvent(RecordingUtils.EVENT_TYPE_TIMESHIFT_END_POSITION, params);
- }
-
- @Override
- public final boolean onTune(Uri channelUri, Bundle params) {
- if (params != null && params.getBoolean(RecordingUtils.BUNDLE_IS_DVR, false)) {
- notifySessionEvent(RecordingUtils.EVENT_TYPE_CONNECTED, null);
- return true;
- } else if (params != null && params.containsKey(RecordingUtils.BUNDLE_MEDIA_URI)) {
- mIsRecordingPlayback = true;
- onPlayMedia(Uri.parse(params.getString(RecordingUtils.BUNDLE_MEDIA_URI)));
- return true;
- } else {
- mIsRecordingPlayback = false;
- return onTune(channelUri);
- }
- }
- }
-}
diff --git a/common/src/com/android/tv/common/recording/RecordingUtils.java b/common/src/com/android/tv/common/recording/RecordingUtils.java
deleted file mode 100644
index ae91659f..00000000
--- a/common/src/com/android/tv/common/recording/RecordingUtils.java
+++ /dev/null
@@ -1,50 +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.common.recording;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-public class RecordingUtils {
- static final int ACTION_START_RECORD = 10055;
- static final int ACTION_STOP_RECORD = 10056;
-
- static final String EVENT_TYPE_CONNECTED = "event_type_connected";
- static final String EVENT_TYPE_TIMESHIFT_END_POSITION = "event_type_timeshift_end_position";
-
- static final String APP_PRIV_CREATE_PLAYBACK_SESSION = "app_priv_create_playback_session";
- static final String APP_PRIV_CREATE_DVR_SESSION = "app_priv_create_dvr_session";
-
- // Type: boolean
- static final String BUNDLE_IS_DVR = "bundle_is_dvr";
- // Type: String (Uri)
- static final String BUNDLE_MEDIA_URI = "bundle_media_uri";
- // Type: String
- static final String BUNDLE_CHANNEL_URI = "bundle_channel_uri";
- // Type: long
- static final String BUNDLE_TIMESHIFT_END_POSITION = "timeshift_end_position";
-
- /**
- * Builds a {@link Bundle} with {@code mediaUri}. If the bundle is sent with tune command,
- * the {@code mediaUri} will be played.
- */
- public static Bundle buildMediaUri(Uri mediaUri) {
- Bundle params = new Bundle();
- params.putString(RecordingUtils.BUNDLE_MEDIA_URI, mediaUri.toString());
- return params;
- }
-}
diff --git a/common/src/com/android/tv/common/recording/TvRecording.java b/common/src/com/android/tv/common/recording/TvRecording.java
deleted file mode 100644
index 28a611a0..00000000
--- a/common/src/com/android/tv/common/recording/TvRecording.java
+++ /dev/null
@@ -1,384 +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.common.recording;
-
-import android.content.Context;
-import android.media.tv.TvContract;
-import android.media.tv.TvView;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.IntDef;
-import android.util.Log;
-import android.view.Surface;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * API for making TV Recordings.
- * This class holds both the API under development and the session app private command magic needed
- * to simulate the API.
- */
-public final class TvRecording {
- private static final String TAG = "TvRecording";
- private static final boolean DEBUG = true; // STOPSHIP(DVR)
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({RECORD_STOP_REASON_DISKFULL, RECORD_STOP_REASON_CONFLICT,
- RECORD_STOP_REASON_CONNECT_FAILED, RECORD_STOP_REASON_DISCONNECTED,
- RECORD_STOP_REASON_UNKNOWN})
- public @interface RecordStopReason {
- }
-
- private static final int FIRST_REASON = 1;
- public static final int RECORD_STOP_REASON_DISKFULL = 1;
- public static final int RECORD_STOP_REASON_CONFLICT = 2;
- public static final int RECORD_STOP_REASON_CONNECT_FAILED = 3;
- public static final int RECORD_STOP_REASON_DISCONNECTED = 4;
- public static final int RECORD_STOP_REASON_UNKNOWN = 5;
- private static final int LAST_REASON = 5;
-
- public abstract static class ClientCallback {
- public void onConnected() { }
-
- public void onDisconnected() { }
-
- public void onRecordStarted(Uri mediaUri) { }
-
- public void onRecordStopped(Uri mediaUri, @RecordStopReason int reason) { }
-
- public void onRecordDeleted(Uri mediaUri) { }
-
- public void onRecordDeleteFailed(Uri mediaUri, int reason) { }
-
- public void onCapabilityReceived(RecordingCapability capability) { }
- }
-
- public interface RecordingClientApi {
- void release();
-
- void startRecord(Uri channelUri, Uri mediaUri);
-
- void stopRecord();
-
- void delete(Uri mediaUri);
-
- void getCapability();
- }
-
- public interface RecordingSessionApi {
- /**
- * Start recording on {@code channelUri}.
- * <p>{@link RecordingSession#notifyRecordStarted(Uri)} should be called as soon as the
- * recording is started.
- */
- void onStartRecord(Uri channelUri, Uri mediaUri);
-
- /**
- * Called when it stops to record.
- */
- void onStopRecord();
-
- /**
- * Called when it is requested to delete {@code mediaUri}.
- */
- void onDelete(Uri mediaUri);
-
- /**
- * Called when the client request {@link RecordingCapability}.
- */
- RecordingCapability onGetCapability();
- }
-
- ///////////
- // BELOW IS IMPLEMENTATION DETAILS OFTEN SPECIFIC TO USING APP PRIVATE COMMANDS
- //////////
-
- private static final String PREFIX = "record_";
-
- private static final String APP_PRIV_DELETE = PREFIX + "app_priv_delete";
- private static final String APP_PRIV_GET_CAPABILITY = PREFIX + "app_priv_get_capability";
- private static final String APP_PRIV_START_RECORD = PREFIX + "app_priv_start_record";
- private static final String APP_PRIV_STOP_RECORD = PREFIX + "app_priv_stop_record";
-
- private static final String EVENT_TYPE_DELETED = PREFIX + "event_type_deleted";
- private static final String EVENT_TYPE_DELETE_FAILED = PREFIX + "event_type_delete_failed";
- private static final String EVENT_TYPE_CAPABILITY_RECEIVED = PREFIX
- + "event_type_capability_received";
- private static final String EVENT_TYPE_RECORD_STARTED = PREFIX + "event_type_record_started";
- private static final String EVENT_TYPE_RECORD_STOPPED = PREFIX + "event_type_record_stopped";
-
- // Type: int
- private static final String BUNDLE_STOPPED_REASON = PREFIX + "stopped_reason";
- // Type: int
- private static final String BUNDLE_DELETE_FAILED_REASON = PREFIX + "delete_failed_reason";
- // Type: RecordingCapability
- private static final String BUNDLE_CAPABILITY = PREFIX + "capability";
-
-
- /**
- * Session linked to {@link TvRecordingClient} to record contents.
- */
- public static abstract class RecordingSession extends RecordingTvInputService.BaseSession
- implements RecordingSessionApi {
- private final static String TAG = "RecordingSession";
-
- public RecordingSession(Context context) {
- super(context);
- }
-
- @Override
- public final boolean onTune(Uri channelUri) {
- // no-op
- return false;
- }
-
- @Override
- public final boolean onSetSurface(Surface surface) {
- // no-op
- return false;
- }
-
- @Override
- public final void onSetStreamVolume(float volume) {
- // no-op
- }
-
- @Override
- public final void onSetCaptionEnabled(boolean enabled) {
- // no-op
- }
-
- /**
- * Notifies when recording starts. It is an response of {@link #onStartRecord}.
- */
- public final void notifyRecordStarted(Uri mediaUri) {
- notifySessionEvent(EVENT_TYPE_RECORD_STARTED, RecordingUtils.buildMediaUri(mediaUri));
- }
-
- /**
- * Notifies when recording is unexpectedly stopped.
- */
- public final void notifyRecordUnexpectedlyStopped(Uri mediaUri, int reason) {
- Bundle params = RecordingUtils.buildMediaUri(mediaUri);
- params.putInt(BUNDLE_STOPPED_REASON, reason);
- notifySessionEvent(EVENT_TYPE_RECORD_STOPPED, params);
- }
-
- /**
- * Notifies when the recording {@code mediaUri} is deleted.
- */
- public final void notifyDeleted(Uri mediaUri) {
- notifySessionEvent(EVENT_TYPE_DELETED, RecordingUtils.buildMediaUri(mediaUri));
- }
-
- /**
- * Notifies when the deletion of the recording {@code mediaUri} is requested through
- * {@link #onDelete} but failed.
- */
- public final void notifyDeleteFailed(Uri mediaUri, int reason) {
- Bundle params = RecordingUtils.buildMediaUri(mediaUri);
- params.putInt(BUNDLE_DELETE_FAILED_REASON, reason);
- notifySessionEvent(EVENT_TYPE_DELETE_FAILED, params);
- }
-
- @Override
- public final void onAppPrivateCommand(String action, Bundle data) {
- if (DEBUG) Log.d(TAG, "onAppPrivateCommand(" + action + ", " + data + ")");
- switch (action) {
- case APP_PRIV_GET_CAPABILITY:
- RecordingCapability capability = onGetCapability();
- Bundle params = new Bundle();
- params.putParcelable(BUNDLE_CAPABILITY, capability);
- notifySessionEvent(EVENT_TYPE_CAPABILITY_RECEIVED, params);
- break;
- case APP_PRIV_DELETE:
- onDelete(Uri.parse(data.getString(RecordingUtils.BUNDLE_CHANNEL_URI)));
- break;
- case APP_PRIV_START_RECORD:
- onStartRecord(Uri.parse(data.getString(RecordingUtils.BUNDLE_CHANNEL_URI)),
- Uri.parse(data.getString(RecordingUtils.BUNDLE_MEDIA_URI)));
- break;
- case APP_PRIV_STOP_RECORD:
- onStopRecord();
- break;
- }
- }
- }
-
- /**
- * A session used for recording.
- */
- public static class TvRecordingClient implements RecordingClientApi {
- private static final String TAG = "DvrSessionClient";
-
- private ClientCallback mCallback;
- private TvView mTvView;
-
- public TvRecordingClient(Context context) {
- if (DEBUG) {
- Log.d(TAG, "creating client");
- }
- mTvView = new TvView(context);
- }
-
- /**
- * Connects the session to a specific input {@code inputId}.
- */
- public void connect(String inputId, ClientCallback callback) {
- if (DEBUG) {
- Log.d(TAG, "connect " + inputId + " with " + callback);
- }
- mCallback = callback;
- Bundle bundle = new Bundle();
- bundle.putBoolean(RecordingUtils.BUNDLE_IS_DVR, true);
- mTvView.tune(inputId, TvContract.buildChannelUri(0), bundle);
- mTvView.sendAppPrivateCommand(RecordingUtils.APP_PRIV_CREATE_DVR_SESSION, null);
- mTvView.setCallback(new TvView.TvInputCallback() {
- @Override
- public void onConnectionFailed(String inputId) {
- if (mCallback == null) {
- return;
- }
- mCallback.onDisconnected();
- }
-
- @Override
- public void onDisconnected(String inputId) {
- if (mCallback == null) {
- return;
- }
- mCallback.onDisconnected();
- }
-
- @Override
- public void onEvent(String inputId, String eventType, Bundle eventArgs) {
- if (mCallback == null) {
- return;
- }
- String mediaUriString = eventArgs == null ? null
- : eventArgs.getString(RecordingUtils.BUNDLE_MEDIA_URI, null);
- Uri mediaUri = mediaUriString == null ? null : Uri.parse(mediaUriString);
- switch (eventType) {
- case RecordingUtils.EVENT_TYPE_CONNECTED:
- mCallback.onConnected();
- break;
- case EVENT_TYPE_DELETED:
- mCallback.onRecordDeleted(mediaUri);
- break;
- case EVENT_TYPE_DELETE_FAILED: {
- // TODO(DVR) use reasons from API
- int reason = eventArgs == null ? 0
- : eventArgs.getInt(BUNDLE_DELETE_FAILED_REASON);
- mCallback.onRecordDeleteFailed(mediaUri, reason);
- break;
- }
- case EVENT_TYPE_CAPABILITY_RECEIVED: {
- RecordingCapability capability = eventArgs
- .getParcelable(BUNDLE_CAPABILITY);
- mCallback.onCapabilityReceived(capability);
- break;
- }
- case EVENT_TYPE_RECORD_STARTED:
- mCallback.onRecordStarted(mediaUri);
- break;
- case EVENT_TYPE_RECORD_STOPPED: {
- int reason = getRecordStopReason(eventArgs);
- mCallback.onRecordStopped(mediaUri, reason);
- break;
- }
- }
- }
-
- // TODO: handle track select.
- });
- }
-
- /**
- * Releases the session.
- */
- @Override
- public void release() {
- if (DEBUG) {
- Log.d(TAG, "release " + this);
- }
- mTvView.reset();
- mCallback = null;
- }
-
- /**
- * Starts recording.
- */
- @Override
- public void startRecord(Uri channelUri, Uri mediaUri) {
- if (DEBUG) {
- Log.d(TAG, "startRecord " + channelUri + ", " + mediaUri);
- }
- Bundle params = RecordingUtils.buildMediaUri(mediaUri);
- params.putString(RecordingUtils.BUNDLE_CHANNEL_URI, channelUri.toString());
- mTvView.sendAppPrivateCommand(APP_PRIV_START_RECORD, params);
- }
-
- /**
- * Stops recording.
- */
- @Override
- public void stopRecord() {
- if (DEBUG) {
- Log.d(TAG, "stopRecord " + this);
- }
- mTvView.sendAppPrivateCommand(APP_PRIV_STOP_RECORD, null);
- }
-
- /**
- * Deletes a recorded media.
- */
- @Override
- public void delete(Uri mediaUri) {
- mTvView.sendAppPrivateCommand(APP_PRIV_DELETE,
- RecordingUtils.buildMediaUri(mediaUri));
- }
-
- @Override
- public void getCapability() {
- mTvView.sendAppPrivateCommand(APP_PRIV_GET_CAPABILITY, null);
- }
-
- @Override
- public String toString() {
- return TvRecordingClient.class.getName() + "{" + "callBack=" + mCallback + "}";
- }
- }
-
- @SuppressWarnings("ResourceType")
- @RecordStopReason
- private static int getRecordStopReason(Bundle eventArgs) {
- if(eventArgs == null) {
- if (DEBUG) Log.d(TAG, "Null stop reason");
- return RECORD_STOP_REASON_UNKNOWN;
- }
- int reason = eventArgs.getInt(BUNDLE_STOPPED_REASON);
- if (reason < FIRST_REASON || reason > LAST_REASON) {
- if (DEBUG) Log.d(TAG, "Unknown stop reason " + reason);
- reason = RECORD_STOP_REASON_UNKNOWN;
- }
- return reason;
- }
-
- private TvRecording() {
- }
-}
diff --git a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
index 28ab97de..5c57d84d 100644
--- a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
+++ b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
@@ -223,6 +223,9 @@ public class FadeAndShortSlide extends Visibility {
float startX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
left, startX, endX, APPEAR_INTERPOLATOR, this);
+ if (slideAnimator == null) {
+ return null;
+ }
mFade.setInterpolator(APPEAR_INTERPOLATOR);
final AnimatorSet set = new AnimatorSet();
set.play(slideAnimator).with(mFade.onAppear(sceneRoot, view, startValues, endValues));
@@ -245,9 +248,15 @@ public class FadeAndShortSlide extends Visibility {
float endX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this);
+ if (slideAnimator == null) { // slideAnimator is null if startX == endX
+ return null;
+ }
+
mFade.setInterpolator(DISAPPEAR_INTERPOLATOR);
- final AnimatorSet set = new AnimatorSet();
final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues);
+ if (fadeAnimator == null) {
+ return null;
+ }
fadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
@@ -255,6 +264,8 @@ public class FadeAndShortSlide extends Visibility {
view.setAlpha(0.0f);
}
});
+
+ final AnimatorSet set = new AnimatorSet();
set.play(slideAnimator).with(fadeAnimator);
Long delay = (Long) startValues.values.get(PROPNAME_DELAY);
if (delay != null) {
diff --git a/common/src/com/android/tv/common/ui/setup/leanback/OnboardingFragment.java b/common/src/com/android/tv/common/ui/setup/leanback/OnboardingFragment.java
deleted file mode 100644
index adbd98c2..00000000
--- a/common/src/com/android/tv/common/ui/setup/leanback/OnboardingFragment.java
+++ /dev/null
@@ -1,531 +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.common.ui.setup.leanback;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.tv.common.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An OnboardingFragment provides a common and simple way to build onboarding screen for
- * applications.
- * <p>
- * <h3>Building the screen</h3>
- * The view structure of onboarding screen is composed of the common parts and custom parts. The
- * common parts are composed of title, description and page navigator and the custom parts are
- * composed of background, contents and foreground.
- * <p>
- * To build the screen views, the inherited class should override:
- * <ul>
- * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
- * size as the screen and the lowest z-order.</li>
- * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
- * the content area at the center of the screen.</li>
- * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
- * size as the screen and the highest z-order</li>
- * </ul>
- * <p>
- * Each of these methods can return {@code null} if the application doesn't want to provide it.
- * <p>
- * <h3>Page information</h3>
- * The onboarding screen may have several pages which explain the functionality of the application.
- * The inherited class should provide the page information by overriding the methods:
- * <p>
- * <ul>
- * <li>{@link #getPageCount} to provide the number of pages.</li>
- * <li>{@link #getPageTitle} to provide the title of the page.</li>
- * <li>{@link #getPageDescription} to provide the description of the page.</li>
- * </ul>
- * <p>
- * <h3><a name="logoAnimation">Logo Splash Animation</a></h3>
- * When onboarding screen appears, the logo splash animation is played by default. The animation
- * fades in the logo image, pauses in a few seconds and fades it out. To support this animation with
- * its own logo image, the inherited class should override the following method.
- * <p>
- * <ul>
- * <li>{@link #getLogoResourceId()}</li>
- * </ul>
- * <p>
- * <h3>Animation</h3>
- * This page has three kinds of animations:
- * <p>
- * <ul>
- * <li><b>Logo splash animation</b> which starts as soon as onboarding screen is shown as described
- * in <a href="#logoAnimation">Logo Splash Animation</a>.</li>
- * <li><b>Page enter animation</b> which runs just after the logo animation finishes. The
- * application can run the animations of their custom views by overriding
- * {@link #onStartEnterAnimation}.</li>
- * <li><b>Page change animation</b> which runs when the page changes. The pages can move backward or
- * forward direction and the application can start the page change animations by overriding
- * {@link #onStartPageChangeAnimation}.</li>
- * </ul>
- * <p>
- * <h3>Finishing the screen</h3>
- * <p>
- * If the user finishes the onboarding screen after navigating all the pages,
- * {@link #onFinishFragment} is called. The inherited class can override this method to show another
- * fragment or activity, or just remove this fragment.
- *
- * @hide
- */
-abstract public class OnboardingFragment extends Fragment {
- private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
- private static final long START_DELAY_TITLE_MS = 33;
- private static final long START_DELAY_DESCRIPTION_MS = 33;
-
- private static final long HEADER_ANIMATION_DURATION_MS = 417;
- private static final long DESCRIPTION_START_DELAY_MS = 33;
- private static final long HEADER_APPEAR_DELAY_MS = 500;
- private static final int SLIDE_DISTANCE = 60;
-
- private static int sSlideDistance;
-
- private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
- private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR
- = new AccelerateInterpolator();
-
- private PagingIndicator mPageIndicator;
- private View mStartButton;
- private ImageView mLogoView;
- private TextView mTitleView;
- private TextView mDescriptionView;
-
- private boolean mEnterTransitionFinished;
- private int mCurrentPageIndex;
-
- private AnimatorSet mAnimator;
-
- /**
- * Called to have the inherited class create its own start animation. The start animation runs
- * after logo splash animation ends.
- */
- abstract protected void onStartEnterAnimation();
-
- private final OnClickListener mOnClickListener = new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (!mEnterTransitionFinished) {
- // Do not change page until the enter transition finishes.
- return;
- }
- if (mCurrentPageIndex == getPageCount() - 1) {
- onFinishFragment();
- } else {
- ++mCurrentPageIndex;
- onPageChanged(mCurrentPageIndex - 1);
- }
- }
- };
-
- private final OnKeyListener mOnKeyListener = new OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (!mEnterTransitionFinished) {
- // Ignore key event until the enter transition finishes.
- return keyCode != KeyEvent.KEYCODE_BACK;
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- return false;
- }
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK:
- if (mCurrentPageIndex == 0) {
- return false;
- }
- // pass through
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (mCurrentPageIndex > 0) {
- --mCurrentPageIndex;
- onPageChanged(mCurrentPageIndex + 1);
- }
- return true;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (mCurrentPageIndex < getPageCount() - 1) {
- ++mCurrentPageIndex;
- onPageChanged(mCurrentPageIndex - 1);
- }
- return true;
- }
- return false;
- }
- };
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, final ViewGroup container,
- Bundle savedInstanceState) {
- ViewGroup view = (ViewGroup) inflater.inflate(R.layout.lb_onboarding_fragment, container,
- false);
- mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
- mPageIndicator.setPageCount(getPageCount());
- mPageIndicator.setOnClickListener(mOnClickListener);
- mPageIndicator.setOnKeyListener(mOnKeyListener);
- mStartButton = view.findViewById(R.id.button_start);
- mStartButton.setOnClickListener(mOnClickListener);
- mStartButton.setOnKeyListener(mOnKeyListener);
- mLogoView = (ImageView) view.findViewById(R.id.logo);
- mLogoView.setImageResource(getLogoResourceId());
- mTitleView = (TextView) view.findViewById(R.id.title);
- mTitleView.setText(getPageTitle(0));
- mDescriptionView = (TextView) view.findViewById(R.id.description);
- mDescriptionView.setText(getPageDescription(0));
- if (sSlideDistance == 0) {
- sSlideDistance = (int) (SLIDE_DISTANCE * getActivity().getResources()
- .getDisplayMetrics().scaledDensity);
- }
- mCurrentPageIndex = 0;
- mPageIndicator.onPageSelected(0, false);
- view.requestFocus();
- if (getLogoResourceId() != 0) {
- container.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
- startLogoAnimation();
- return true;
- }
- });
- } else {
- onLogoAnimationFinished();
- }
- return view;
- }
-
- private void startLogoAnimation() {
- mLogoView.setVisibility(View.VISIBLE);
- Animator inAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_logo_enter);
- Animator outAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_logo_exit);
- outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
- AnimatorSet animator = new AnimatorSet();
- animator.playSequentially(inAnimator, outAnimator);
- animator.setTarget(mLogoView);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mEnterTransitionFinished = true;
- if (getActivity() != null) {
- onLogoAnimationFinished();
- onStartEnterAnimation();
- }
- }
- });
- animator.start();
- }
-
- private void onLogoAnimationFinished() {
- mLogoView.setVisibility(View.GONE);
- // Create custom views.
- LayoutInflater inflater = LayoutInflater.from(getActivity());
- ViewGroup backgroundContainer = (ViewGroup) getView().findViewById(
- R.id.background_container);
- View background = onCreateBackgroundView(inflater, backgroundContainer);
- if (background != null) {
- backgroundContainer.setVisibility(View.VISIBLE);
- backgroundContainer.addView(background);
- }
- ViewGroup contentContainer = (ViewGroup) getView().findViewById(R.id.content_container);
- View content = onCreateContentView(inflater, contentContainer);
- if (content != null) {
- contentContainer.setVisibility(View.VISIBLE);
- contentContainer.addView(content);
- }
- ViewGroup foregroundContainer = (ViewGroup) getView().findViewById(
- R.id.foreground_container);
- View foreground = onCreateForegroundView(inflater, foregroundContainer);
- if (foreground != null) {
- foregroundContainer.setVisibility(View.VISIBLE);
- foregroundContainer.addView(foreground);
- }
- // Make views visible which were invisible while logo animation is running.
- getView().findViewById(R.id.page_container).setVisibility(View.VISIBLE);
- getView().findViewById(R.id.content_container).setVisibility(View.VISIBLE);
-
- List<Animator> animators = new ArrayList<>();
- Animator animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_page_indicator_enter);
- if (getPageCount() <= 1) {
- // Start button
- mStartButton.setVisibility(View.VISIBLE);
- animator.setTarget(mStartButton);
- } else {
- // Page indicator
- mPageIndicator.setVisibility(View.VISIBLE);
- animator.setTarget(mPageIndicator);
- }
- animators.add(animator);
- // Header title
- View view = getActivity().findViewById(R.id.title);
- view.setAlpha(0);
- animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_title_enter);
- animator.setStartDelay(START_DELAY_TITLE_MS);
- animator.setTarget(view);
- animators.add(animator);
- // Header description
- view = getActivity().findViewById(R.id.description);
- view.setAlpha(0);
- animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_description_enter);
- animator.setStartDelay(START_DELAY_DESCRIPTION_MS);
- animator.setTarget(view);
- animators.add(animator);
- mAnimator = new AnimatorSet();
- mAnimator.playTogether(animators);
- mAnimator.start();
- onStartEnterAnimation();
- // Search focus and give the focus to the appropriate child which has become visible.
- getView().requestFocus();
- }
-
- /**
- * Returns the page count.
- *
- * @return The page count.
- */
- abstract protected int getPageCount();
-
- /**
- * Returns the title of the given page.
- *
- * @param pageIndex The page index.
- *
- * @return The title of the page.
- */
- abstract protected String getPageTitle(int pageIndex);
-
- /**
- * Returns the description of the given page.
- *
- * @param pageIndex The page index.
- *
- * @return The description of the page.
- */
- abstract protected String getPageDescription(int pageIndex);
-
- /**
- * Returns the index of the current page.
- *
- * @return The index of the current page.
- */
- protected final int getCurrentPageIndex() {
- return mCurrentPageIndex;
- }
-
- /**
- * Returns the resource ID of the splash logo image.
- *
- * @return The resource ID of the splash logo image.
- */
- abstract protected int getLogoResourceId();
-
- /**
- * Called to have the inherited class create background view. This is optional and the fragment
- * which doesn't have the background view can return {@code null}. This is called inside
- * {@link #onCreateView}.
- *
- * @param inflater The LayoutInflater object that can be used to inflate the views,
- * @param container The parent view that the additional views are attached to.The fragment
- * should not add the view by itself.
- *
- * @return The background view for the onboarding screen, or {@code null}.
- */
- @Nullable
- abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
-
- /**
- * Called to have the inherited class create content view. This is optional and the fragment
- * which doesn't have the content view can return {@code null}. This is called inside
- * {@link #onCreateView}.
- *
- * <p>The content view would be located at the center of the screen.
- *
- * @param inflater The LayoutInflater object that can be used to inflate the views,
- * @param container The parent view that the additional views are attached to.The fragment
- * should not add the view by itself.
- *
- * @return The content view for the onboarding screen, or {@code null}.
- */
- @Nullable
- abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
-
- /**
- * Called to have the inherited class create foreground view. This is optional and the fragment
- * which doesn't need the foreground view can return {@code null}. This is called inside
- * {@link #onCreateView}.
- *
- * <p>This foreground view would have the highest z-order.
- *
- * @param inflater The LayoutInflater object that can be used to inflate the views,
- * @param container The parent view that the additional views are attached to.The fragment
- * should not add the view by itself.
- *
- * @return The foreground view for the onboarding screen, or {@code null}.
- */
- @Nullable
- abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
-
- /**
- * Called when the onboarding flow finishes.
- */
- protected void onFinishFragment() { }
-
- /**
- * Called when the page changes.
- */
- private void onPageChanged(int previousPage) {
- if (mAnimator != null) {
- mAnimator.end();
- }
- mPageIndicator.onPageSelected(mCurrentPageIndex, true);
-
- List<Animator> animators = new ArrayList<>();
- // Header animation
- Animator fadeAnimator = null;
- if (previousPage < getCurrentPageIndex()) {
- // sliding to left
- animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
- animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
- DESCRIPTION_START_DELAY_MS));
- animators.add(createAnimator(mTitleView, true, Gravity.END,
- HEADER_APPEAR_DELAY_MS));
- animators.add(createAnimator(mDescriptionView, true, Gravity.END,
- HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
- } else {
- // sliding to right
- animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
- animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
- DESCRIPTION_START_DELAY_MS));
- animators.add(createAnimator(mTitleView, true, Gravity.START,
- HEADER_APPEAR_DELAY_MS));
- animators.add(createAnimator(mDescriptionView, true, Gravity.START,
- HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
- }
- final int currentPageIndex = getCurrentPageIndex();
- fadeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mTitleView.setText(getPageTitle(currentPageIndex));
- mDescriptionView.setText(getPageDescription(currentPageIndex));
- }
- });
-
- // Animator for switching between page indicator and button.
- if (getCurrentPageIndex() == getPageCount() - 1) {
- mStartButton.setVisibility(View.VISIBLE);
- Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_page_indicator_fade_out);
- navigatorFadeOutAnimator.setTarget(mPageIndicator);
- Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_start_button_fade_in);
- buttonFadeInAnimator.setTarget(mStartButton);
- animators.add(navigatorFadeOutAnimator);
- navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPageIndicator.setVisibility(View.GONE);
- }
- });
- animators.add(buttonFadeInAnimator);
- } else if (previousPage == getPageCount() - 1) {
- mPageIndicator.setVisibility(View.VISIBLE);
- Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_page_indicator_fade_in);
- navigatorFadeInAnimator.setTarget(mPageIndicator);
- Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.lb_onboarding_start_button_fade_out);
- buttonFadeOutAnimator.setTarget(mStartButton);
- buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mStartButton.setVisibility(View.GONE);
- }
- });
- mAnimator = new AnimatorSet();
- mAnimator.playTogether(navigatorFadeInAnimator, buttonFadeOutAnimator);
- mAnimator.start();
- }
- mAnimator = new AnimatorSet();
- mAnimator.playTogether(animators);
- mAnimator.start();
- onStartPageChangeAnimation(previousPage);
- }
-
- private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
- long startDelay) {
- boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
- boolean slideRight = (isLtr && slideDirection == Gravity.END)
- || (!isLtr && slideDirection == Gravity.START)
- || slideDirection == Gravity.RIGHT;
- Animator fadeAnimator;
- Animator slideAnimator;
- if (fadeIn) {
- fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
- slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
- slideRight ? sSlideDistance : -sSlideDistance, 0);
- fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
- slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
- } else {
- fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
- slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
- slideRight ? sSlideDistance : -sSlideDistance);
- fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
- slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
- }
- fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
- fadeAnimator.setTarget(view);
- slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
- slideAnimator.setTarget(view);
- AnimatorSet animator = new AnimatorSet();
- animator.playTogether(fadeAnimator, slideAnimator);
- if (startDelay > 0) {
- animator.setStartDelay(startDelay);
- }
- return animator;
- }
-
- /**
- * Called to have the inherited class run its own page change animation
- *
- * @param previousPage The previous page.
- */
- abstract protected void onStartPageChangeAnimation(int previousPage);
-}
diff --git a/common/src/com/android/tv/common/ui/setup/leanback/PagingIndicator.java b/common/src/com/android/tv/common/ui/setup/leanback/PagingIndicator.java
deleted file mode 100644
index e2c9be72..00000000
--- a/common/src/com/android/tv/common/ui/setup/leanback/PagingIndicator.java
+++ /dev/null
@@ -1,377 +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.common.ui.setup.leanback;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.os.Build;
-import android.support.annotation.ColorInt;
-import android.support.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.tv.common.R;
-import com.android.tv.common.annotation.UsedByReflection;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A page indicator with dots.
- * @hide
- */
-public class PagingIndicator extends View {
- // attribute
- private final int mDotDiameter;
- private final int mDotRadius;
- private final int mDotGap;
- private final int mArrowDiameter;
- private final int mArrowRadius;
- private final int mArrowGap;
- private final int mShadowRadius;
- private Dot[] mDots;
- // X position when the dot is selected.
- private int[] mDotSelectedX;
- // X position when the dot is located to the left of the selected dot.
- private int[] mDotSelectedLeftX;
- // X position when the dot is located to the right of the selected dot.
- private int[] mDotSelectedRightX;
- private int mDotCenterY;
-
- // state
- private int mPageCount;
- private int mCurrentPage;
- private int mPreviousPage;
-
- // drawing
- @ColorInt
- private final int mDotFgSelectColor;
- private final Paint mBgPaint;
- private final Paint mFgPaint;
- private final Animator mShowAnimator;
- private final Animator mHideAnimator;
- private final AnimatorSet mAnimator = new AnimatorSet();
- private final Bitmap mArrow;
- private final Rect mArrowRect;
- private final float mArrowToBgRatio;
-
- public PagingIndicator(Context context) {
- this(context, null, 0);
- }
-
- public PagingIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PagingIndicator(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Resources res = getResources();
- mDotRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_radius);
- mDotDiameter = mDotRadius * 2;
- mDotGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_gap);
- mArrowGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_gap);
- mArrowDiameter = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_diameter);
- mArrowRadius = mArrowDiameter / 2;
- mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mDotFgSelectColor = res.getColor(R.color.lb_page_indicator_arrow_background);
- int bgColor = res.getColor(R.color.lb_page_indicator_dot);
- int shadowColor = res.getColor(R.color.lb_page_indicator_arrow_shadow);
- mBgPaint.setColor(bgColor);
- mShadowRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_radius);
- mFgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- int shadowOffset = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_offset);
- mFgPaint.setShadowLayer(mShadowRadius, shadowOffset, shadowOffset, shadowColor);
- mArrow = BitmapFactory.decodeResource(res, R.drawable.lb_ic_nav_arrow);
- mArrowRect = new Rect(0, 0, mArrow.getWidth(), mArrow.getHeight());
- mArrowToBgRatio = (float) mArrow.getWidth() / (float) mArrowDiameter;
- // Initialize animations.
- List<Animator> animators = new ArrayList<>();
- mShowAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.animator.lb_page_indicator_dot_show);
- mHideAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.animator.lb_page_indicator_dot_hide);
- animators.add(mShowAnimator);
- animators.add(mHideAnimator);
- mAnimator.playTogether(animators);
- // Use software layer to show shadows.
- setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- }
-
- /**
- * Sets the page count.
- */
- public void setPageCount(int pages) {
- if (pages <= 0) {
- throw new IllegalArgumentException("The page count should be a positive integer");
- }
- mPageCount = pages;
- mDots = new Dot[mPageCount];
- for (int i = 0; i < mPageCount; ++i) {
- mDots[i] = new Dot();
- }
- calculateDotPositions();
- setSelectedPage(0);
- }
-
- /**
- * Called when the page has been selected.
- */
- public void onPageSelected(int pageIndex, boolean withAnimation) {
- if (mCurrentPage == pageIndex) {
- return;
- }
- if (mAnimator.isStarted()) {
- mAnimator.end();
- }
- mPreviousPage = mCurrentPage;
- if (withAnimation) {
- mHideAnimator.setTarget(mDots[mPreviousPage]);
- mShowAnimator.setTarget(mDots[pageIndex]);
- mAnimator.start();
- }
- setSelectedPage(pageIndex);
- }
-
- private void calculateDotPositions() {
- int left = getPaddingLeft();
- int top = getPaddingTop();
- int right = getWidth() - getPaddingRight();
- int requiredWidth = getRequiredWidth();
- int mid = (left + right) / 2;
- int startLeft = mid - requiredWidth / 2;
- mDotSelectedX = new int[mPageCount];
- mDotSelectedLeftX = new int[mPageCount];
- mDotSelectedRightX = new int[mPageCount];
- // mDotSelectedX[0] should be mDotSelectedLeftX[-1] + mArrowGap
- mDotSelectedX[0] = startLeft + mDotRadius - mDotGap + mArrowGap;
- mDotSelectedLeftX[0] = startLeft + mDotRadius;
- mDotSelectedRightX[0] = 0;
- for (int i = 1; i < mPageCount; i++) {
- mDotSelectedX[i] = mDotSelectedLeftX[i - 1] + mArrowGap;
- mDotSelectedLeftX[i] = mDotSelectedLeftX[i - 1] + mDotGap;
- mDotSelectedRightX[i] = mDotSelectedX[i - 1] + mArrowGap;
- }
- mDotCenterY = top + mArrowRadius;
- adjustDotPosition();
- }
-
- @VisibleForTesting
- int getPageCount() {
- return mPageCount;
- }
-
- @VisibleForTesting
- int[] getDotSelectedX() {
- return mDotSelectedX;
- }
-
- @VisibleForTesting
- int[] getDotSelectedLeftX() {
- return mDotSelectedLeftX;
- }
-
- @VisibleForTesting
- int[] getDotSelectedRightX() {
- return mDotSelectedRightX;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int desiredHeight = getDesiredHeight();
- int height;
- switch (MeasureSpec.getMode(heightMeasureSpec)) {
- case MeasureSpec.EXACTLY:
- height = MeasureSpec.getSize(heightMeasureSpec);
- break;
- case MeasureSpec.AT_MOST:
- height = Math.min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec));
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- height = desiredHeight;
- break;
- }
- int desiredWidth = getDesiredWidth();
- int width;
- switch (MeasureSpec.getMode(widthMeasureSpec)) {
- case MeasureSpec.EXACTLY:
- width = MeasureSpec.getSize(widthMeasureSpec);
- break;
- case MeasureSpec.AT_MOST:
- width = Math.min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec));
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- width = desiredWidth;
- break;
- }
- setMeasuredDimension(width, height);
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
- setMeasuredDimension(width, height);
- calculateDotPositions();
- }
-
- private int getDesiredHeight() {
- return getPaddingTop() + mArrowDiameter + getPaddingBottom() + mShadowRadius;
- }
-
- private int getRequiredWidth() {
- return 2 * mDotRadius + 2 * mArrowGap + (mPageCount - 3) * mDotGap;
- }
-
- private int getDesiredWidth() {
- return getPaddingLeft() + getRequiredWidth() + getPaddingRight();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- for (int i = 0; i < mPageCount; ++i) {
- mDots[i].draw(canvas);
- }
- }
-
- private void setSelectedPage(int now) {
- if (now == mCurrentPage) {
- return;
- }
-
- mCurrentPage = now;
- adjustDotPosition();
- }
-
- private void adjustDotPosition() {
- for (int i = 0; i < mCurrentPage; ++i) {
- mDots[i].deselect();
- mDots[i].mDirection = i == mPreviousPage ? Dot.LEFT : Dot.RIGHT;
- mDots[i].mCenterX = mDotSelectedLeftX[i];
- }
- mDots[mCurrentPage].select();
- mDots[mCurrentPage].mDirection = mPreviousPage < mCurrentPage ? Dot.LEFT : Dot.RIGHT;
- mDots[mCurrentPage].mCenterX = mDotSelectedX[mCurrentPage];
- for (int i = mCurrentPage + 1; i < mPageCount; ++i) {
- mDots[i].deselect();
- mDots[i].mDirection = Dot.RIGHT;
- mDots[i].mCenterX = mDotSelectedRightX[i];
- }
- }
-
- public class Dot {
- static final float LEFT = -1;
- static final float RIGHT = 1;
-
- float mAlpha;
- @ColorInt
- int mBgColor;
- @ColorInt
- int mFgColor;
- float mTranslationX;
- float mCenterX;
- float mDiameter;
- float mRadius;
- float mArrowImageRadius;
- float mDirection = RIGHT;
-
- void select() {
- mTranslationX = 0.0f;
- mCenterX = 0.0f;
- mDiameter = mArrowDiameter;
- mRadius = mArrowRadius;
- mArrowImageRadius = mRadius * mArrowToBgRatio;
- mAlpha = 1.0f;
- adjustAlpha();
- }
-
- void deselect() {
- mTranslationX = 0.0f;
- mCenterX = 0.0f;
- mDiameter = mDotDiameter;
- mRadius = mDotRadius;
- mArrowImageRadius = mRadius * mArrowToBgRatio;
- mAlpha = 0.0f;
- adjustAlpha();
- }
-
- public void adjustAlpha() {
- int alpha = Math.round(0xFF * mAlpha);
- int red = Color.red(mDotFgSelectColor);
- int green = Color.green(mDotFgSelectColor);
- int blue = Color.blue(mDotFgSelectColor);
- mFgColor = Color.argb(alpha, red, green, blue);
- }
-
- @UsedByReflection
- public float getAlpha() {
- return mAlpha;
- }
-
- @UsedByReflection
- public void setAlpha(float alpha) {
- this.mAlpha = alpha;
- adjustAlpha();
- invalidate();
- }
-
- @UsedByReflection
- public float getTranslationX() {
- return mTranslationX;
- }
-
- @UsedByReflection
- public void setTranslationX(float translationX) {
- this.mTranslationX = translationX * mDirection;
- invalidate();
- }
-
- @UsedByReflection
- public float getDiameter() {
- return mDiameter;
- }
-
- @UsedByReflection
- public void setDiameter(float diameter) {
- this.mDiameter = diameter;
- this.mRadius = diameter / 2;
- this.mArrowImageRadius = diameter / 2 * mArrowToBgRatio;
- invalidate();
- }
-
- void draw(Canvas canvas) {
- float centerX = mCenterX + mTranslationX;
- canvas.drawCircle(centerX, mDotCenterY, mRadius, mBgPaint);
- if (mAlpha > 0) {
- mFgPaint.setColor(mFgColor);
- canvas.drawCircle(centerX, mDotCenterY, mRadius, mFgPaint);
- canvas.drawBitmap(mArrow, mArrowRect, new Rect((int) (centerX - mArrowImageRadius),
- (int) (mDotCenterY - mArrowImageRadius),
- (int) (centerX + mArrowImageRadius),
- (int) (mDotCenterY + mArrowImageRadius)), null);
- }
- }
- }
-}