diff options
Diffstat (limited to 'common/src/com/android/tv')
84 files changed, 3220 insertions, 378 deletions
diff --git a/common/src/com/android/tv/common/BaseApplication.java b/common/src/com/android/tv/common/BaseApplication.java index 71c9b4d7..45c32567 100644 --- a/common/src/com/android/tv/common/BaseApplication.java +++ b/common/src/com/android/tv/common/BaseApplication.java @@ -17,10 +17,7 @@ package com.android.tv.common; import android.annotation.TargetApi; -import android.app.Application; import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; import android.os.Build; import android.os.StrictMode; import android.support.annotation.VisibleForTesting; @@ -30,9 +27,10 @@ import com.android.tv.common.util.Clock; import com.android.tv.common.util.CommonUtils; import com.android.tv.common.util.Debug; import com.android.tv.common.util.SystemProperties; +import dagger.android.DaggerApplication; /** The base application class for Live TV applications. */ -public abstract class BaseApplication extends Application implements BaseSingletons { +public abstract class BaseApplication extends DaggerApplication implements BaseSingletons { private RecordingStorageStatusManager mRecordingStorageStatusManager; /** @@ -41,7 +39,13 @@ public abstract class BaseApplication extends Application implements BaseSinglet */ @VisibleForTesting public static BaseSingletons sSingletons; - /** Returns the {@link BaseSingletons} using the application context. */ + /** + * Returns the {@link BaseSingletons} using the application context. + * + * @deprecated use {@link com.android.tv.common.singletons.HasSingletons#get(Class, Context)} + * instead + */ + @Deprecated public static BaseSingletons getSingletons(Context context) { // STOP-SHIP: changing the method to protected once the Tuner application is created. // No need to be "synchronized" because this doesn't create any instance. @@ -65,8 +69,15 @@ public abstract class BaseApplication extends Application implements BaseSinglet StrictMode.ThreadPolicy.Builder threadPolicyBuilder = new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog(); // TODO(b/69565157): Turn penaltyDeath on for VMPolicy when tests are fixed. + // TODO(b/120840665): Restore detecting untagged network sockets StrictMode.VmPolicy.Builder vmPolicyBuilder = - new StrictMode.VmPolicy.Builder().detectAll().penaltyLog(); + new StrictMode.VmPolicy.Builder() + .detectActivityLeaks() + .detectLeakedClosableObjects() + .detectLeakedRegistrationObjects() + .detectFileUriExposure() + .detectContentUriWithoutPermission() + .penaltyLog(); if (!CommonUtils.isRunningInTest()) { threadPolicyBuilder.penaltyDialog(); @@ -77,14 +88,6 @@ public abstract class BaseApplication extends Application implements BaseSinglet if (CommonFeatures.DVR.isEnabled(this)) { getRecordingStorageStatusManager(); } - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - // Fetch remote config - getRemoteConfig().fetch(null); - return null; - } - }.execute(); } @Override @@ -101,7 +104,4 @@ public abstract class BaseApplication extends Application implements BaseSinglet } return mRecordingStorageStatusManager; } - - @Override - public abstract Intent getTunerSetupIntent(Context context); } diff --git a/common/src/com/android/tv/common/BaseSingletons.java b/common/src/com/android/tv/common/BaseSingletons.java index e735cdb4..10530617 100644 --- a/common/src/com/android/tv/common/BaseSingletons.java +++ b/common/src/com/android/tv/common/BaseSingletons.java @@ -16,20 +16,17 @@ package com.android.tv.common; -import android.content.Context; -import android.content.Intent; -import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig; +import com.android.tv.common.buildtype.HasBuildType; +import com.android.tv.common.flags.has.HasCloudEpgFlags; +import com.android.tv.common.flags.has.HasConcurrentDvrPlaybackFlags; import com.android.tv.common.recording.RecordingStorageStatusManager; import com.android.tv.common.util.Clock; /** Injection point for the base app */ -public interface BaseSingletons extends HasRemoteConfig { +public interface BaseSingletons + extends HasCloudEpgFlags, HasBuildType, HasConcurrentDvrPlaybackFlags { Clock getClock(); RecordingStorageStatusManager getRecordingStorageStatusManager(); - - Intent getTunerSetupIntent(Context context); - - String getEmbeddedTunerInputId(); } diff --git a/common/src/com/android/tv/common/BuildConfig.java b/common/src/com/android/tv/common/BuildConfig.java new file mode 100644 index 00000000..b3ad002b --- /dev/null +++ b/common/src/com/android/tv/common/BuildConfig.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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; + +/* Hard coded BuildConfig. */ +public final class BuildConfig { + public static final boolean DEBUG = true; + public static final boolean ENG = false; + public static final boolean NO_JNI_TEST = false; + public static final boolean AOSP = true; + + private BuildConfig() {} +}
\ No newline at end of file diff --git a/common/src/com/android/tv/common/CommonConstants.java b/common/src/com/android/tv/common/CommonConstants.java index ac379d18..43d9851b 100644 --- a/common/src/com/android/tv/common/CommonConstants.java +++ b/common/src/com/android/tv/common/CommonConstants.java @@ -19,10 +19,20 @@ package com.android.tv.common; /** Constants for common use in apps and tests. */ public final class CommonConstants { + @Deprecated // TODO(b/121158110) refactor so this is not needed. public static final String BASE_PACKAGE = +// AOSP_Comment_Out !BuildConfig.AOSP +// AOSP_Comment_Out ? "com.google.android.tv" +// AOSP_Comment_Out : "com.android.tv"; /** A constant for the key of the extra data for the app linking intent. */ public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri"; + /** + * Video is unavailable because the source is not physically connected, for example the HDMI + * cable is not connected. + */ + public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5; + private CommonConstants() {} } diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java index cfdb8e4d..f2fda69c 100644 --- a/common/src/com/android/tv/common/TvContentRatingCache.java +++ b/common/src/com/android/tv/common/TvContentRatingCache.java @@ -23,9 +23,8 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.tv.common.memory.MemoryManageable; -import java.util.ArrayList; +import com.google.common.collect.ImmutableList; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -42,19 +41,19 @@ public final class TvContentRatingCache implements MemoryManageable { } // @GuardedBy("TvContentRatingCache.this") - private final Map<String, TvContentRating[]> mRatingsMultiMap = new ArrayMap<>(); + private final Map<String, ImmutableList<TvContentRating>> mRatingsMultiMap = new ArrayMap<>(); /** * Returns an array TvContentRatings from a string of comma separated set of rating strings - * creating each from {@link TvContentRating#unflattenFromString(String)} if needed. Returns - * {@code null} if the string is empty or contains no valid ratings. + * creating each from {@link TvContentRating#unflattenFromString(String)} if needed or an empty + * list if the string is empty or contains no valid ratings. */ - @Nullable - public synchronized TvContentRating[] getRatings(String commaSeparatedRatings) { + public synchronized ImmutableList<TvContentRating> getRatings( + @Nullable String commaSeparatedRatings) { if (TextUtils.isEmpty(commaSeparatedRatings)) { - return null; + return ImmutableList.of(); } - TvContentRating[] tvContentRatings; + ImmutableList<TvContentRating> tvContentRatings; if (mRatingsMultiMap.containsKey(commaSeparatedRatings)) { tvContentRatings = mRatingsMultiMap.get(commaSeparatedRatings); } else { @@ -76,12 +75,13 @@ public final class TvContentRatingCache implements MemoryManageable { /** Returns a sorted array of TvContentRatings from a comma separated string of ratings. */ @VisibleForTesting - static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) { + static ImmutableList<TvContentRating> stringToContentRatings( + @Nullable String commaSeparatedRatings) { if (TextUtils.isEmpty(commaSeparatedRatings)) { - return null; + return ImmutableList.of(); } Set<String> ratingStrings = getSortedSetFromCsv(commaSeparatedRatings); - List<TvContentRating> contentRatings = new ArrayList<>(); + ImmutableList.Builder<TvContentRating> contentRatings = ImmutableList.builder(); for (String rating : ratingStrings) { try { contentRatings.add(TvContentRating.unflattenFromString(rating)); @@ -89,9 +89,7 @@ public final class TvContentRatingCache implements MemoryManageable { Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e); } } - return contentRatings.size() == 0 - ? null - : contentRatings.toArray(new TvContentRating[contentRatings.size()]); + return contentRatings.build(); } private static Set<String> getSortedSetFromCsv(String commaSeparatedRatings) { @@ -118,19 +116,17 @@ public final class TvContentRatingCache implements MemoryManageable { * Returns a string of each flattened content rating, sorted and concatenated together with a * comma. */ - public static String contentRatingsToString(TvContentRating[] contentRatings) { - if (contentRatings == null || contentRatings.length == 0) { + @Nullable + public static String contentRatingsToString( + @Nullable ImmutableList<TvContentRating> contentRatings) { + if (contentRatings == null) { return null; } - String[] ratingStrings = new String[contentRatings.length]; - for (int i = 0; i < contentRatings.length; i++) { - ratingStrings[i] = contentRatings[i].flattenToString(); - } - if (ratingStrings.length == 1) { - return ratingStrings[0]; - } else { - return TextUtils.join(",", toSortedSet(ratingStrings)); + SortedSet<String> ratingStrings = new TreeSet<>(); + for (TvContentRating rating : contentRatings) { + ratingStrings.add(rating.flattenToString()); } + return TextUtils.join(",", ratingStrings); } @Override diff --git a/common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java b/common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java new file mode 100644 index 00000000..8d39b3a6 --- /dev/null +++ b/common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.buildtype; + +/** {@code AOSP} {@link HasBuildType}. */ +public class AospBuildTypeProvider implements HasBuildType { + + @Override + public BuildType getBuildType() { + return BuildType.AOSP; + } +} diff --git a/common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java b/common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java new file mode 100644 index 00000000..5f18794c --- /dev/null +++ b/common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.buildtype; + +/** {@code ENG} {@link HasBuildType}. */ +public class EngBuildTypeProvider implements HasBuildType { + + @Override + public BuildType getBuildType() { + return BuildType.ENG; + } +} diff --git a/common/src/com/android/tv/common/buildtype/HasBuildType.java b/common/src/com/android/tv/common/buildtype/HasBuildType.java new file mode 100644 index 00000000..7d5677c9 --- /dev/null +++ b/common/src/com/android/tv/common/buildtype/HasBuildType.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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.buildtype; + +/** + * The provides a {@link BuildType} for selecting features in code. + * + * <p>This is considered an anti-pattern and new usages should be discouraged. + */ +public interface HasBuildType { + + /** Compile time constant for build type. */ + enum BuildType { + AOSP, + ENG, + NO_JNI_TEST, + PROD + } + + BuildType getBuildType(); +} diff --git a/common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java b/common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java new file mode 100644 index 00000000..1620af20 --- /dev/null +++ b/common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.buildtype; + +/** {@code NO_JNI_TEST} {@link HasBuildType}. */ +public class NoJniTestBuildTypeProvider implements HasBuildType { + + @Override + public BuildType getBuildType() { + return BuildType.NO_JNI_TEST; + } +} diff --git a/common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java b/common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java new file mode 100644 index 00000000..16db3263 --- /dev/null +++ b/common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.buildtype; + +/** {@code Prod} {@link HasBuildType}. */ +public class ProdBuildTypeProvider implements HasBuildType { + + @Override + public BuildType getBuildType() { + return BuildType.PROD; + } +} diff --git a/common/src/com/android/tv/common/compat/README.md b/common/src/com/android/tv/common/compat/README.md new file mode 100644 index 00000000..8d87d83c --- /dev/null +++ b/common/src/com/android/tv/common/compat/README.md @@ -0,0 +1,7 @@ +# TIF Compatibility Library + +This is the temporary location for the of Compatibility Library while it is under development. +It will eventually move to a support library location. + + +See go/tif-compat-proposal
\ No newline at end of file diff --git a/common/src/com/android/tv/common/compat/RecordingSessionCompat.java b/common/src/com/android/tv/common/compat/RecordingSessionCompat.java new file mode 100644 index 00000000..6941e47b --- /dev/null +++ b/common/src/com/android/tv/common/compat/RecordingSessionCompat.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 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.compat; + +import android.content.Context; +import android.media.tv.TvInputService.RecordingSession; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.RequiresApi; +import com.android.tv.common.compat.api.RecordingSessionCompatCommands; +import com.android.tv.common.compat.api.RecordingSessionCompatEvents; +import com.android.tv.common.compat.api.SessionEventNotifier; +import com.android.tv.common.compat.internal.RecordingSessionCompatProcessor; + +/** + * TIF Compatibility for {@link RecordingSession}. + * + * <p>Extends {@code RecordingSession} in a backwards compatible way. + */ +@RequiresApi(api = VERSION_CODES.N) +public abstract class RecordingSessionCompat extends RecordingSession + implements SessionEventNotifier, + RecordingSessionCompatCommands, + RecordingSessionCompatEvents { + + private final RecordingSessionCompatProcessor mProcessor; + + public RecordingSessionCompat(Context context) { + super(context); + mProcessor = new RecordingSessionCompatProcessor(this, this); + } + + @Override + public void onAppPrivateCommand(String action, Bundle data) { + if (!mProcessor.handleAppPrivateCommand(action, data)) { + super.onAppPrivateCommand(action, data); + } + } + + /** Display a debug message to the session for display on dev builds only */ + @Override + public void onDevMessage(String message) {} + + /** Notify the client to Display a message in the application as a toast on dev builds only. */ + @Override + public void notifyDevToast(String message) { + mProcessor.notifyDevToast(message); + } + + /** Notify the client Recording started. */ + @Override + public void notifyRecordingStarted(String uri) { + mProcessor.notifyRecordingStarted(uri); + } +} diff --git a/common/src/com/android/tv/common/compat/TisSessionCompat.java b/common/src/com/android/tv/common/compat/TisSessionCompat.java new file mode 100644 index 00000000..97f4fb32 --- /dev/null +++ b/common/src/com/android/tv/common/compat/TisSessionCompat.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 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.compat; + +import android.content.Context; +import android.media.tv.TvInputService.Session; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.RequiresApi; +import com.android.tv.common.compat.api.SessionCompatCommands; +import com.android.tv.common.compat.api.SessionCompatEvents; +import com.android.tv.common.compat.api.SessionEventNotifier; +import com.android.tv.common.compat.internal.TifSessionCompatProcessor; + +/** + * TIF Compatibility for {@link Session}. + * + * <p>Extends {@code Session} in a backwards compatible way. + */ +@RequiresApi(api = VERSION_CODES.LOLLIPOP) +public abstract class TisSessionCompat extends Session + implements SessionEventNotifier, SessionCompatCommands, SessionCompatEvents { + + private final TifSessionCompatProcessor mTifCompatProcessor; + + public TisSessionCompat(Context context) { + super(context); + mTifCompatProcessor = new TifSessionCompatProcessor(this, this); + } + + @Override + public void onAppPrivateCommand(String action, Bundle data) { + if (!mTifCompatProcessor.handleAppPrivateCommand(action, data)) { + super.onAppPrivateCommand(action, data); + } + } + + @Override + public void onDevMessage(String message) {} + + @Override + public void notifyDevToast(String message) { + mTifCompatProcessor.notifyDevToast(message); + } + + /** + * Notify the application with current signal strength. + * + * <p>At each {MainActivity#tune(boolean)}, the signal strength is implicitly reset to {@link + * TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED}. If a TV input supports reporting signal + * strength, it should set the signal strength to {@link + * TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN} in + * {TunerSessionWorker#prepareTune(TunerChannel, String)}, until a valid strength is available. + * + * @param value The current signal strength. Valid values are {@link + * TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED}, {@link + * TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN}, and 0 - 100 inclusive. + */ + @Override + public void notifySignalStrength(int value) { + mTifCompatProcessor.notifySignalStrength(value); + } +} diff --git a/common/src/com/android/tv/common/compat/TvInputConstantCompat.java b/common/src/com/android/tv/common/compat/TvInputConstantCompat.java new file mode 100644 index 00000000..251e8481 --- /dev/null +++ b/common/src/com/android/tv/common/compat/TvInputConstantCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.compat; + +/** Temp TIF Compatibility for {@link TvInputManager} constants. */ +public class TvInputConstantCompat { + + /** + * Status for {@link TisSessionCompat#notifySignalStrength(int)} and + * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}: + * + * <p>SIGNAL_STRENGTH_NOT_USED means the TV Input does not report signal strength. Each onTune + * command implicitly resets the TV App's signal strength state to SIGNAL_STRENGTH_NOT_USED. + */ + public static final int SIGNAL_STRENGTH_NOT_USED = -3; + + /** + * Status for {@link TisSessionCompat#notifySignalStrength(int)} and + * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}: + * + * <p>SIGNAL_STRENGTH_ERROR means exception/error when handling signal strength. + */ + public static final int SIGNAL_STRENGTH_ERROR = -2; + + /** + * Status for {@link TisSessionCompat#notifySignalStrength(int)} and + * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}: + * + * <p>SIGNAL_STRENGTH_UNKNOWN means the TV Input supports signal strength, but does not + * currently know what the strength is. + */ + public static final int SIGNAL_STRENGTH_UNKNOWN = -1; +} diff --git a/common/src/com/android/tv/common/compat/TvInputInfoCompat.java b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java new file mode 100644 index 00000000..685a3ed9 --- /dev/null +++ b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 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.compat; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputService; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.xmlpull.v1.XmlPullParser; + +/** + * TIF Compatibility for {@link TvInputInfo}. + */ +public class TvInputInfoCompat { + private static final String TAG = "TvInputInfoCompat"; + private static final String ATTRIBUTE_NAMESPACE_ANDROID = + "http://schemas.android.com/apk/res/android"; + private static final String TV_INPUT_XML_START_TAG_NAME = "tv-input"; + private static final String TV_INPUT_EXTRA_XML_START_TAG_NAME = "extra"; + private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_VALUE = "value"; + private static final String ATTRIBUTE_NAME_AUDIO_ONLY = + "com.android.tv.common.compat.tvinputinfocompat.audioOnly"; + + private final Context mContext; + private final TvInputInfo mTvInputInfo; + private final boolean mAudioOnly; + + public TvInputInfoCompat(Context context, TvInputInfo tvInputInfo) { + mContext = context; + mTvInputInfo = tvInputInfo; + // TODO(b/112938832): use tvInputInfo.isAudioOnly() when SDK is updated + mAudioOnly = Boolean.parseBoolean(getExtras().get(ATTRIBUTE_NAME_AUDIO_ONLY)); + } + + public TvInputInfo getTvInputInfo() { + return mTvInputInfo; + } + + public boolean isAudioOnly() { + return mAudioOnly; + } + + public int getType() { + return mTvInputInfo.getType(); + } + + @VisibleForTesting + public Map<String, String> getExtras() { + ServiceInfo si = mTvInputInfo.getServiceInfo(); + + try { + XmlPullParser parser = getXmlResourceParser(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + if (!TV_INPUT_XML_START_TAG_NAME.equals(parser.getName())) { + Log.w(TAG, "Meta-data does not start with " + TV_INPUT_XML_START_TAG_NAME + + " tag for " + si.name); + return Collections.emptyMap(); + } + // <tv-input> start tag found + Map<String, String> extras = new HashMap<>(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.END_TAG + && TV_INPUT_XML_START_TAG_NAME.equals(parser.getName())) { + // </tv-input> end tag found + return extras; + } + if (type == XmlPullParser.START_TAG + && TV_INPUT_EXTRA_XML_START_TAG_NAME.equals(parser.getName())) { + String extraName = + parser.getAttributeValue(ATTRIBUTE_NAMESPACE_ANDROID, ATTRIBUTE_NAME); + String extraValue = + parser.getAttributeValue(ATTRIBUTE_NAMESPACE_ANDROID, ATTRIBUTE_VALUE); + if (extraName != null && extraValue != null) { + extras.put(extraName, extraValue); + } + } + } + + } catch (Exception e) { + Log.e(TAG, "Failed to get extras of " + mTvInputInfo.getId() , e); + } + return Collections.emptyMap(); + } + + @VisibleForTesting + XmlPullParser getXmlResourceParser() { + ServiceInfo si = mTvInputInfo.getServiceInfo(); + PackageManager pm = mContext.getPackageManager(); + return si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA); + } +} diff --git a/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java b/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java new file mode 100644 index 00000000..143ff25c --- /dev/null +++ b/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 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.compat; + +import android.content.Context; +import android.media.tv.TvRecordingClient; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.RequiresApi; +import android.util.ArrayMap; +import com.android.tv.common.compat.api.PrivateCommandSender; +import com.android.tv.common.compat.api.RecordingClientCallbackCompatEvents; +import com.android.tv.common.compat.api.TvRecordingClientCompatCommands; +import com.android.tv.common.compat.internal.RecordingClientCompatProcessor; + +/** + * TIF Compatibility for {@link TvRecordingClient}. + * + * <p>Extends {@code TvRecordingClient} in a backwards compatible way. + */ +@RequiresApi(api = VERSION_CODES.N) +public class TvRecordingClientCompat extends TvRecordingClient + implements TvRecordingClientCompatCommands, PrivateCommandSender { + + private final RecordingClientCompatProcessor mProcessor; + + /** + * Creates a new TvRecordingClient object. + * + * @param context The application context to create a TvRecordingClient with. + * @param tag A short name for debugging purposes. + * @param callback The callback to receive recording status changes. + * @param handler The handler to invoke the callback on. + */ + public TvRecordingClientCompat( + Context context, String tag, RecordingCallback callback, Handler handler) { + super(context, tag, callback, handler); + RecordingCallbackCompat compatEvents = + callback instanceof RecordingCallbackCompat + ? (RecordingCallbackCompat) callback + : null; + mProcessor = new RecordingClientCompatProcessor(this, compatEvents); + if (compatEvents != null) { + compatEvents.mClientCompatProcessor = mProcessor; + } + } + + /** Tell the session to Display a debug message dev builds only. */ + @Override + public void devMessage(String message) { + mProcessor.devMessage(message); + } + + /** + * TIF Compatibility for {@link RecordingCallback}. + * + * <p>Extends {@code RecordingCallback} in a backwards compatible way. + */ + public static class RecordingCallbackCompat extends RecordingCallback + implements RecordingClientCallbackCompatEvents { + private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>(); + private RecordingClientCompatProcessor mClientCompatProcessor; + + @Override + public void onEvent(String inputId, String eventType, Bundle eventArgs) { + if (mClientCompatProcessor != null + && !mClientCompatProcessor.handleEvent(inputId, eventType, eventArgs)) { + super.onEvent(inputId, eventType, eventArgs); + } + } + + public int getTifCompatVersionForInput(String inputId) { + return inputCompatVersionMap.containsKey(inputId) + ? inputCompatVersionMap.get(inputId) + : 0; + } + + /** Display a message as a toast on dev builds only. */ + @Override + public void onDevToast(String inputId, String message) {} + + /** Recording started. */ + @Override + public void onRecordingStarted(String inputId, String recUri) {} + } +} diff --git a/common/src/com/android/tv/common/compat/TvViewCompat.java b/common/src/com/android/tv/common/compat/TvViewCompat.java new file mode 100644 index 00000000..f44564d3 --- /dev/null +++ b/common/src/com/android/tv/common/compat/TvViewCompat.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 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.compat; + +import android.content.Context; +import android.media.tv.TvView; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.RequiresApi; +import android.util.ArrayMap; +import android.util.AttributeSet; +import com.android.tv.common.compat.api.PrivateCommandSender; +import com.android.tv.common.compat.api.TvInputCallbackCompatEvents; +import com.android.tv.common.compat.api.TvViewCompatCommands; +import com.android.tv.common.compat.internal.TvViewCompatProcessor; + +/** + * TIF Compatibility for {@link TvView}. + * + * <p>Extends {@code TvView} in a backwards compatible way. + */ +@RequiresApi(api = VERSION_CODES.LOLLIPOP) +public class TvViewCompat extends TvView implements TvViewCompatCommands, PrivateCommandSender { + + private final TvViewCompatProcessor mTvViewCompatProcessor; + + public TvViewCompat(Context context) { + this(context, null); + } + + public TvViewCompat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TvViewCompat(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mTvViewCompatProcessor = new TvViewCompatProcessor(this); + } + + @Override + public void setCallback(TvInputCallback callback) { + super.setCallback(callback); + if (callback instanceof TvInputCallbackCompat) { + TvInputCallbackCompat compatEvents = (TvInputCallbackCompat) callback; + mTvViewCompatProcessor.setCallback(compatEvents); + compatEvents.mTvViewCompatProcessor = mTvViewCompatProcessor; + } + } + + @Override + public void devMessage(String message) { + mTvViewCompatProcessor.devMessage(message); + } + + /** + * TIF Compatibility for {@link TvInputCallback}. + * + * <p>Extends {@code TvInputCallback} in a backwards compatible way. + */ + public static class TvInputCallbackCompat extends TvInputCallback + implements TvInputCallbackCompatEvents { + private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>(); + private TvViewCompatProcessor mTvViewCompatProcessor; + + @Override + public void onEvent(String inputId, String eventType, Bundle eventArgs) { + if (mTvViewCompatProcessor != null + && !mTvViewCompatProcessor.handleEvent(inputId, eventType, eventArgs)) { + super.onEvent(inputId, eventType, eventArgs); + } + } + + public int getTifCompatVersionForInput(String inputId) { + return inputCompatVersionMap.containsKey(inputId) + ? inputCompatVersionMap.get(inputId) + : 0; + } + + @Override + public void onDevToast(String inputId, String message) {} + + /** + * This is called when the signal strength is notified. + * + * @param inputId The ID of the TV input bound to this view. + * @param value The current signal strength. Should be one of the followings. + * <ul> + * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED} + * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_ERROR} + * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN} + * <li>{int [0, 100]} + * </ul> + */ + @Override + public void onSignalStrength(String inputId, int value) {} + } +} diff --git a/common/src/com/android/tv/common/compat/api/PrivateCommandSender.java b/common/src/com/android/tv/common/compat/api/PrivateCommandSender.java new file mode 100644 index 00000000..11de970f --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/PrivateCommandSender.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.compat.api; + +import android.os.Bundle; + +/** Sends a command from the TV App to a {@link android.media.tv.TvInputService.Session} */ +public interface PrivateCommandSender { + + void sendAppPrivateCommand(String action, Bundle data); +} diff --git a/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java b/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java new file mode 100644 index 00000000..753703c4 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** + * {@link android.media.tv.TvRecordingClient} implements this to receive notification from a {@link + * android.media.tv.TvInputService.RecordingSession} + */ +public interface RecordingClientCallbackCompatEvents { + /** Display a message in the application as a toast on dev builds only */ + void onDevToast(String inputId, String message); + + void onRecordingStarted(String inputId, String recUri); +} diff --git a/common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java new file mode 100644 index 00000000..9deaa41f --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Commands sent from the TV App to {@link android.media.tv.TvInputService.RecordingSession} */ +public interface RecordingSessionCompatCommands { + + void onDevMessage(String message); +} diff --git a/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java new file mode 100644 index 00000000..812bba62 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Events sent from the {@link android.media.tv.TvInputService.RecordingSession} to the TV App. */ +public interface RecordingSessionCompatEvents { + + void notifyDevToast(String message); + + void notifyRecordingStarted(String value); +} diff --git a/common/src/com/android/tv/common/compat/api/SessionCompatCommands.java b/common/src/com/android/tv/common/compat/api/SessionCompatCommands.java new file mode 100644 index 00000000..bef4ad27 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/SessionCompatCommands.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Commands sent from the TV App to {@link android.media.tv.TvInputService.Session} */ +public interface SessionCompatCommands { + + void onDevMessage(String message); +} diff --git a/common/src/com/android/tv/common/compat/api/SessionCompatEvents.java b/common/src/com/android/tv/common/compat/api/SessionCompatEvents.java new file mode 100644 index 00000000..a3af8f3c --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/SessionCompatEvents.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Events sent from the {@link android.media.tv.TvInputService.Session} to the TV App. */ +public interface SessionCompatEvents { + + void notifyDevToast(String message); + + void notifySignalStrength(int value); +} diff --git a/common/src/com/android/tv/common/compat/api/SessionEventNotifier.java b/common/src/com/android/tv/common/compat/api/SessionEventNotifier.java new file mode 100644 index 00000000..66c5c3a8 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/SessionEventNotifier.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.compat.api; + +import android.os.Bundle; + +/** Sends events from a {@link android.media.tv.TvInputService.Session} to the TV App. */ +public interface SessionEventNotifier { + + void notifySessionEvent(String event, Bundle data); +} diff --git a/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java b/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java new file mode 100644 index 00000000..e6b241b7 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** + * {@link android.media.tv.TvView.TvInputCallback} implements this to receive notification from a + * {@link android.media.tv.TvInputService.Session} + */ +public interface TvInputCallbackCompatEvents { + void onDevToast(String inputId, String message); + + void onSignalStrength(String inputId, int value); +} diff --git a/common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java b/common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java new file mode 100644 index 00000000..c1852165 --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Commands sent from the TV App to {@link android.media.tv.TvInputService.RecordingSession} */ +public interface TvRecordingClientCompatCommands { + + void devMessage(String message); +} diff --git a/common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java b/common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java new file mode 100644 index 00000000..5abc6bce --- /dev/null +++ b/common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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.compat.api; + +/** Commands sent from the TV App to {@link android.media.tv.TvInputService.Session} */ +public interface TvViewCompatCommands { + + void devMessage(String message); +} diff --git a/common/src/com/android/tv/common/compat/internal/Constants.java b/common/src/com/android/tv/common/compat/internal/Constants.java new file mode 100644 index 00000000..993822c1 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/Constants.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.compat.internal; +/** Static constants use by the TIF compat library */ +final class Constants { + static final String ACTION_GET_VERSION = "com.android.tv.common.compat.action.GET_VERSION"; + static final String EVENT_GET_VERSION = "com.android.tv.common.compat.event.GET_VERSION"; + static final String ACTION_COMPAT_ON = "com.android.tv.common.compat.action.COMPAT_ON"; + static final String EVENT_COMPAT_NOTIFY = "com.android.tv.common.compat.event.COMPAT_NOTIFY"; + static final String EVENT_COMPAT_NOTIFY_ERROR = + "com.android.tv.common.compat.event.COMPAT_NOTIFY_ERROR"; + static final int TIF_COMPAT_VERSION = 1; + + private Constants() {} +} diff --git a/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java new file mode 100644 index 00000000..f83228c8 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.support.annotation.Nullable; +import android.util.Log; +import com.android.tv.common.compat.api.PrivateCommandSender; +import com.android.tv.common.compat.api.RecordingClientCallbackCompatEvents; +import com.android.tv.common.compat.api.TvViewCompatCommands; +import com.android.tv.common.compat.internal.Commands.OnDevMessage; +import com.android.tv.common.compat.internal.Commands.PrivateCommand; +import com.android.tv.common.compat.internal.RecordingEvents.NotifyDevToast; +import com.android.tv.common.compat.internal.RecordingEvents.RecordingSessionEvent; + +/** + * Sends {@link RecordingCommands} to the {@link android.media.tv.TvInputService.RecordingSession} + * via {@link PrivateCommandSender} and receives notification events from the session forwarding + * them to {@link RecordingClientCallbackCompatEvents} + */ +public final class RecordingClientCompatProcessor + extends ViewCompatProcessor<PrivateCommand, RecordingSessionEvent> + implements TvViewCompatCommands { + private static final String TAG = "RecordingClientCompatProcessor"; + + @Nullable private final RecordingClientCallbackCompatEvents mCallback; + + public RecordingClientCompatProcessor( + PrivateCommandSender commandSender, + @Nullable RecordingClientCallbackCompatEvents callback) { + super(commandSender, RecordingSessionEvent.parser()); + mCallback = callback; + } + + @Override + public void devMessage(String message) { + OnDevMessage devMessage = OnDevMessage.newBuilder().setMessage(message).build(); + PrivateCommand privateCommand = + createPrivateCommandCommand().setOnDevMessage(devMessage).build(); + sendCompatCommand(privateCommand); + } + + private PrivateCommand.Builder createPrivateCommandCommand() { + return PrivateCommand.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION); + } + + @Override + protected final void handleSessionEvent(String inputId, RecordingSessionEvent sessionEvent) { + switch (sessionEvent.getEventCase()) { + case NOTIFY_DEV_MESSAGE: + handle(inputId, sessionEvent.getNotifyDevMessage()); + break; + case RECORDING_STARTED: + handle(inputId, sessionEvent.getRecordingStarted()); + break; + + case EVENT_NOT_SET: + Log.w(TAG, "Error event not set compat notify "); + } + } + + private void handle(String inputId, NotifyDevToast devToast) { + if (devToast != null && mCallback != null) { + mCallback.onDevToast(inputId, devToast.getMessage()); + } + } + + private void handle(String inputId, RecordingEvents.RecordingStarted recStart) { + if (recStart != null && mCallback != null) { + mCallback.onRecordingStarted(inputId, recStart.getUri()); + } + } +} diff --git a/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java new file mode 100644 index 00000000..84ec5503 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.util.Log; +import com.android.tv.common.compat.api.RecordingSessionCompatCommands; +import com.android.tv.common.compat.api.RecordingSessionCompatEvents; +import com.android.tv.common.compat.api.SessionEventNotifier; +import com.android.tv.common.compat.internal.RecordingCommands.PrivateRecordingCommand; +import com.android.tv.common.compat.internal.RecordingEvents.NotifyDevToast; +import com.android.tv.common.compat.internal.RecordingEvents.RecordingSessionEvent; +import com.android.tv.common.compat.internal.RecordingEvents.RecordingStarted; + +/** + * Sends {@link RecordingSessionCompatEvents} to the TV App via {@link SessionEventNotifier} and + * receives Commands from TV App forwarding them to {@link RecordingSessionCompatProcessor} + */ +public final class RecordingSessionCompatProcessor + extends SessionCompatProcessor<PrivateRecordingCommand, RecordingSessionEvent> + implements RecordingSessionCompatEvents { + + private static final String TAG = "RecordingSessionCompatProc"; + + private final RecordingSessionCompatCommands mRecordingSessionOnCompat; + + public RecordingSessionCompatProcessor( + SessionEventNotifier sessionEventNotifier, + RecordingSessionCompatCommands recordingSessionOnCompat) { + super(sessionEventNotifier, PrivateRecordingCommand.parser()); + mRecordingSessionOnCompat = recordingSessionOnCompat; + } + + @Override + protected void onCompat(PrivateRecordingCommand privateCommand) { + switch (privateCommand.getCommandCase()) { + case ON_DEV_MESSAGE: + mRecordingSessionOnCompat.onDevMessage( + privateCommand.getOnDevMessage().getMessage()); + break; + case COMMAND_NOT_SET: + Log.w(TAG, "Command not set "); + } + } + + @Override + public void notifyDevToast(String message) { + NotifyDevToast devMessage = NotifyDevToast.newBuilder().setMessage(message).build(); + RecordingSessionEvent sessionEvent = + createSessionEvent().setNotifyDevMessage(devMessage).build(); + notifyCompat(sessionEvent); + } + + @Override + public void notifyRecordingStarted(String uri) { + RecordingStarted event = RecordingStarted.newBuilder().setUri(uri).build(); + RecordingSessionEvent sessionEvent = + createSessionEvent().setRecordingStarted(event).build(); + notifyCompat(sessionEvent); + } + + private RecordingSessionEvent.Builder createSessionEvent() { + + return RecordingSessionEvent.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION); + } +} diff --git a/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java new file mode 100644 index 00000000..7f27a243 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.os.Bundle; +import android.util.Log; +import com.android.tv.common.compat.api.SessionEventNotifier; +import com.google.protobuf.GeneratedMessageLite; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Parser; + +/** + * Sends {@code events} to the TV App via {@link SessionEventNotifier} and receives {@code commands} + * from TV App. + */ +abstract class SessionCompatProcessor< + C extends GeneratedMessageLite<C, ?>, E extends GeneratedMessageLite<E, ?>> { + private static final String TAG = "SessionCompatProcessor"; + private final SessionEventNotifier mSessionEventNotifier; + private final Parser<C> mCommandParser; + + SessionCompatProcessor(SessionEventNotifier sessionEventNotifier, Parser<C> commandParser) { + mSessionEventNotifier = sessionEventNotifier; + mCommandParser = commandParser; + } + + public final boolean handleAppPrivateCommand(String action, Bundle data) { + switch (action) { + case Constants.ACTION_GET_VERSION: + Bundle response = new Bundle(); + response.putInt(Constants.EVENT_GET_VERSION, Constants.TIF_COMPAT_VERSION); + mSessionEventNotifier.notifySessionEvent(Constants.EVENT_GET_VERSION, response); + return true; + case Constants.ACTION_COMPAT_ON: + byte[] bytes = data.getByteArray(Constants.ACTION_COMPAT_ON); + try { + C privateCommand = mCommandParser.parseFrom(bytes); + onCompat(privateCommand); + } catch (InvalidProtocolBufferException e) { + Log.w(TAG, "Error parsing compat data", e); + } + + return true; + default: + return false; + } + } + + abstract void onCompat(C privateCommand); + + final void notifyCompat(E event) { + Bundle response = new Bundle(); + try { + byte[] bytes = event.toByteArray(); + response.putByteArray(Constants.EVENT_COMPAT_NOTIFY, bytes); + } catch (Exception e) { + Log.w(TAG, "Failed to send " + event, e); + response.putString(Constants.EVENT_COMPAT_NOTIFY_ERROR, e.getMessage()); + } + mSessionEventNotifier.notifySessionEvent(Constants.EVENT_COMPAT_NOTIFY, response); + } +} diff --git a/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java new file mode 100644 index 00000000..dd7a3b3e --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.util.Log; +import com.android.tv.common.compat.api.SessionCompatCommands; +import com.android.tv.common.compat.api.SessionCompatEvents; +import com.android.tv.common.compat.api.SessionEventNotifier; +import com.android.tv.common.compat.internal.Commands.PrivateCommand; +import com.android.tv.common.compat.internal.Events.NotifyDevToast; +import com.android.tv.common.compat.internal.Events.NotifySignalStrength; +import com.android.tv.common.compat.internal.Events.SessionEvent; + +/** + * Sends {@link SessionCompatEvents} to the TV App via {@link SessionEventNotifier} and receives + * Commands from TV App forwarding them to {@link SessionCompatCommands} + */ +public final class TifSessionCompatProcessor + extends SessionCompatProcessor<PrivateCommand, SessionEvent> + implements SessionCompatEvents { + + private static final String TAG = "TifSessionCompatProcessor"; + + private final SessionCompatCommands mSessionOnCompat; + + public TifSessionCompatProcessor( + SessionEventNotifier sessionEventNotifier, SessionCompatCommands sessionOnCompat) { + super(sessionEventNotifier, PrivateCommand.parser()); + mSessionOnCompat = sessionOnCompat; + } + + @Override + protected void onCompat(Commands.PrivateCommand privateCommand) { + switch (privateCommand.getCommandCase()) { + case ON_DEV_MESSAGE: + mSessionOnCompat.onDevMessage(privateCommand.getOnDevMessage().getMessage()); + break; + case COMMAND_NOT_SET: + Log.w(TAG, "Command not set "); + } + } + + @Override + public void notifyDevToast(String message) { + NotifyDevToast devMessage = NotifyDevToast.newBuilder().setMessage(message).build(); + SessionEvent sessionEvent = createSessionEvent().setNotifyDevMessage(devMessage).build(); + notifyCompat(sessionEvent); + } + + @Override + public void notifySignalStrength(int value) { + NotifySignalStrength signalStrength = + NotifySignalStrength.newBuilder().setSignalStrength(value).build(); + SessionEvent sessionEvent = + createSessionEvent().setNotifySignalStrength(signalStrength).build(); + notifyCompat(sessionEvent); + } + + private SessionEvent.Builder createSessionEvent() { + return SessionEvent.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION); + } +} diff --git a/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java new file mode 100644 index 00000000..382f8d8a --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.support.annotation.NonNull; +import android.util.Log; +import com.android.tv.common.compat.api.PrivateCommandSender; +import com.android.tv.common.compat.api.TvInputCallbackCompatEvents; +import com.android.tv.common.compat.api.TvViewCompatCommands; +import com.android.tv.common.compat.internal.Commands.OnDevMessage; +import com.android.tv.common.compat.internal.Commands.PrivateCommand; +import com.android.tv.common.compat.internal.Events.NotifyDevToast; +import com.android.tv.common.compat.internal.Events.NotifySignalStrength; +import com.android.tv.common.compat.internal.Events.SessionEvent; + +/** + * Sends {@link TvViewCompatCommands} to the {@link android.media.tv.TvInputService.Session} via + * {@link PrivateCommandSender} and receives notification events from the session forwarding them to + * {@link TvInputCallbackCompatEvents} + */ +public final class TvViewCompatProcessor extends ViewCompatProcessor<PrivateCommand, SessionEvent> + implements TvViewCompatCommands { + private static final String TAG = "TvViewCompatProcessor"; + + private TvInputCallbackCompatEvents mCallback; + + public TvViewCompatProcessor(PrivateCommandSender commandSender) { + super(commandSender, SessionEvent.parser()); + } + + @Override + public void devMessage(String message) { + OnDevMessage devMessage = Commands.OnDevMessage.newBuilder().setMessage(message).build(); + Commands.PrivateCommand privateCommand = + createPrivateCommandCommand().setOnDevMessage(devMessage).build(); + sendCompatCommand(privateCommand); + } + + @NonNull + private PrivateCommand.Builder createPrivateCommandCommand() { + return PrivateCommand.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION); + } + + public void onDevToast(String inputId, String message) {} + + public void onSignalStrength(String inputId, int value) {} + + @Override + protected final void handleSessionEvent(String inputId, Events.SessionEvent sessionEvent) { + switch (sessionEvent.getEventCase()) { + case NOTIFY_DEV_MESSAGE: + handle(inputId, sessionEvent.getNotifyDevMessage()); + break; + case NOTIFY_SIGNAL_STRENGTH: + handle(inputId, sessionEvent.getNotifySignalStrength()); + break; + case EVENT_NOT_SET: + Log.w(TAG, "Error event not set compat notify "); + } + } + + private void handle(String inputId, NotifyDevToast devToast) { + if (devToast != null && mCallback != null) { + mCallback.onDevToast(inputId, devToast.getMessage()); + } + } + + private void handle(String inputId, NotifySignalStrength signalStrength) { + if (signalStrength != null && mCallback != null) { + mCallback.onSignalStrength(inputId, signalStrength.getSignalStrength()); + } + } + + public void setCallback(TvInputCallbackCompatEvents callback) { + this.mCallback = callback; + } +} diff --git a/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java new file mode 100644 index 00000000..dc6bbfa5 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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.compat.internal; + +import android.os.Bundle; +import android.util.ArrayMap; +import android.util.Log; +import com.android.tv.common.compat.api.PrivateCommandSender; +import com.google.protobuf.GeneratedMessageLite; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Parser; + +/** + * Sends {@code commands} to the {@code session} via {@link PrivateCommandSender} and receives + * notification events from the session forwarding them to {@link + * com.android.tv.common.compat.api.TvInputCallbackCompatEvents} + */ +abstract class ViewCompatProcessor< + C extends GeneratedMessageLite<C, ?>, E extends GeneratedMessageLite<E, ?>> { + private static final String TAG = "ViewCompatProcessor"; + private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>(); + + private final Parser<E> mEventParser; + private final PrivateCommandSender mCommandSender; + + ViewCompatProcessor(PrivateCommandSender commandSender, Parser<E> eventParser) { + mCommandSender = commandSender; + mEventParser = eventParser; + } + + private final E sessionEventFromBundle(Bundle eventArgs) throws InvalidProtocolBufferException { + + byte[] protoBytes = eventArgs.getByteArray(Constants.EVENT_COMPAT_NOTIFY); + return protoBytes == null || protoBytes.length == 0 + ? null + : mEventParser.parseFrom(protoBytes); + } + + final void sendCompatCommand(C privateCommand) { + try { + Bundle data = new Bundle(); + data.putByteArray(Constants.ACTION_COMPAT_ON, privateCommand.toByteArray()); + mCommandSender.sendAppPrivateCommand(Constants.ACTION_COMPAT_ON, data); + } catch (Exception e) { + Log.w(TAG, "Error sending compat action " + privateCommand, e); + } + } + + public boolean handleEvent(String inputId, String eventType, Bundle eventArgs) { + switch (eventType) { + case Constants.EVENT_GET_VERSION: + int version = eventArgs.getInt(Constants.EVENT_GET_VERSION, 0); + inputCompatVersionMap.put(inputId, version); + return true; + case Constants.EVENT_COMPAT_NOTIFY: + try { + E sessionEvent = sessionEventFromBundle(eventArgs); + if (sessionEvent != null) { + handleSessionEvent(inputId, sessionEvent); + } else { + String errorMessage = + eventArgs.getString(Constants.EVENT_COMPAT_NOTIFY_ERROR); + Log.w(TAG, "Error sent in compat notify " + errorMessage); + } + + } catch (InvalidProtocolBufferException e) { + Log.w(TAG, "Error parsing in compat notify for " + inputId); + } + + return true; + default: + return false; + } + } + + protected abstract void handleSessionEvent(String inputId, E sessionEvent); +} diff --git a/common/src/com/android/tv/common/compat/internal/recording_commands.proto b/common/src/com/android/tv/common/compat/internal/recording_commands.proto new file mode 100644 index 00000000..ce59bfa0 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/recording_commands.proto @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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. + */ + +// A set of Private Commands to send to a TVInputService.Session, in particular +// support new features on older devices. NOTE: this proto is internal to this +// package and should not be used outside it. + +syntax = "proto3"; +package android.tv.common.compat.internal; + +option java_outer_classname = "RecordingCommands"; +option java_package = "com.android.tv.common.compat.internal"; + +// Wraps messages for sending to a session a private command. +message PrivateRecordingCommand { + uint32 compat_version = 1; + + oneof command { + OnDevMessage on_dev_message = 2; + } +} + +// Display a debug message dev builds only. +message OnDevMessage { + string message = 1; +} diff --git a/common/src/com/android/tv/common/compat/internal/recording_events.proto b/common/src/com/android/tv/common/compat/internal/recording_events.proto new file mode 100644 index 00000000..68db5ddf --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/recording_events.proto @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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. + */ + +// A set of Session events to send from a TVInputService.Session, in particular +// support new features on older devices. NOTE: this proto is internal to this +// package and should not be used outside it. +syntax = "proto3"; +package android.tv.common.compat.internal; + +option java_outer_classname = "RecordingEvents"; +option java_package = "com.android.tv.common.compat.internal"; + +// Wraps messages for sending from a session as an Event. +// RecordingSessionCompat will have a notify{EventMessageName} for each event +// TvRecordingClientCompat will have a on{EventMessageName} for each event + +message RecordingSessionEvent { + uint32 compat_version = 1; + + oneof event { + NotifyDevToast notify_dev_message = 2; + RecordingStarted recording_started = 3; + } +} + +// Display a message as a toast on dev builds only +message NotifyDevToast { + string message = 1; +} + +// Recording started. +message RecordingStarted { + // Recording URI. + string uri = 1; +} + diff --git a/common/src/com/android/tv/common/compat/internal/tif_commands.proto b/common/src/com/android/tv/common/compat/internal/tif_commands.proto new file mode 100644 index 00000000..d5867703 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/tif_commands.proto @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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. + */ + +// A set of Private Commands to send to a TVInputService.Session, in particular +// support new features on older devices. NOTE: this proto is internal to this +// package and should not be used outside it. + +syntax = "proto3"; +package android.tv.common.compat.internal; + +option java_outer_classname = "Commands"; +option java_package = "com.android.tv.common.compat.internal"; + +// Wraps messages for sending to a session a private command. +message PrivateCommand { + uint32 compat_version = 1; + + oneof command { + OnDevMessage on_dev_message = 2; + } +} + +// Sends a debug message to the session for display on dev builds only +message OnDevMessage { + string message = 1; +} diff --git a/common/src/com/android/tv/common/compat/internal/tif_events.proto b/common/src/com/android/tv/common/compat/internal/tif_events.proto new file mode 100644 index 00000000..6e71ae11 --- /dev/null +++ b/common/src/com/android/tv/common/compat/internal/tif_events.proto @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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. + */ + +// A set of Session events to send from a TVInputService.Session, in particular +// support new features on older devices. NOTE: this proto is internal to this +// package and should not be used outside it. +syntax = "proto3"; +package android.tv.common.compat.internal; + +option java_outer_classname = "Events"; +option java_package = "com.android.tv.common.compat.internal"; + +// Wraps messages for sending from a session as an Event. +message SessionEvent { + uint32 compat_version = 1; + + oneof event { + NotifyDevToast notify_dev_message = 2; + NotifySignalStrength notify_signal_strength = 3; + } +} + +// Send a message to the application to be displayed as a toast on dev builds +// only +message NotifyDevToast { + string message = 1; +} + +// Notifies the TV Application the current signal strength. +message NotifySignalStrength { + // The signal strength as a percent (0 to 100), + // with -1 meaning unknown, -2 meaning not used. + int32 signal_strength = 1; +} diff --git a/common/src/com/android/tv/common/config/DefaultConfigManager.java b/common/src/com/android/tv/common/config/DefaultConfigManager.java deleted file mode 100644 index ae240855..00000000 --- a/common/src/com/android/tv/common/config/DefaultConfigManager.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.common.config; - -import android.content.Context; -import com.android.tv.common.config.api.RemoteConfig; - -/** Stub Remote Config. */ -public class DefaultConfigManager { - public static final long DEFAULT_LONG_VALUE = 0; - - public static DefaultConfigManager createInstance(Context context) { - return new DefaultConfigManager(); - } - - private StubRemoteConfig mRemoteConfig = new StubRemoteConfig(); - - public RemoteConfig getRemoteConfig() { - return mRemoteConfig; - } - - private static class StubRemoteConfig implements RemoteConfig { - @Override - public void fetch(OnRemoteConfigUpdatedListener listener) {} - - @Override - public String getString(String key) { - return null; - } - - @Override - public boolean getBoolean(String key) { - return false; - } - - @Override - public long getLong(String key) { - return DEFAULT_LONG_VALUE; - } - - @Override - public long getLong(String key, long defaultValue) { - return defaultValue; - } - } -} diff --git a/common/src/com/android/tv/common/config/RemoteConfigFeature.java b/common/src/com/android/tv/common/config/RemoteConfigFeature.java deleted file mode 100644 index 2ea381f0..00000000 --- a/common/src/com/android/tv/common/config/RemoteConfigFeature.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.common.config; - -import android.content.Context; -import com.android.tv.common.BaseApplication; -import com.android.tv.common.feature.Feature; - -/** - * A {@link Feature} controlled by a {@link com.android.tv.common.config.api.RemoteConfig} boolean. - */ -public class RemoteConfigFeature implements Feature { - private final String mKey; - - /** Creates a {@link RemoteConfigFeature for the {@code key}. */ - public static RemoteConfigFeature fromKey(String key) { - return new RemoteConfigFeature(key); - } - - private RemoteConfigFeature(String key) { - mKey = key; - } - - @Override - public boolean isEnabled(Context context) { - return BaseApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey); - } -} diff --git a/common/src/com/android/tv/common/config/api/RemoteConfig.java b/common/src/com/android/tv/common/config/api/RemoteConfig.java deleted file mode 100644 index 74597f9d..00000000 --- a/common/src/com/android/tv/common/config/api/RemoteConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.common.config.api; - -/** - * Manages Live TV Configuration, allowing remote updates. - * - * <p>This is a thin wrapper around <a - * href="https://firebase.google.com/docs/remote-config/"></a>Firebase Remote Config</a> - */ -public interface RemoteConfig { - - /** Used to inject a remote config */ - interface HasRemoteConfig { - RemoteConfig getRemoteConfig(); - } - - /** Notified on successful completion of a {@link #fetch)} */ - interface OnRemoteConfigUpdatedListener { - void onRemoteConfigUpdated(); - } - - /** Starts a fetch and notifies {@code listener} on successful completion. */ - void fetch(OnRemoteConfigUpdatedListener listener); - - /** Gets value as a string corresponding to the specified key. */ - String getString(String key); - - /** Gets value as a boolean corresponding to the specified key. */ - boolean getBoolean(String key); - - /** Gets value as a long corresponding to the specified key. */ - long getLong(String key); - - /** - * Gets value as a long corresponding to the specified key. Returns the defaultValue if no value - * is found. - */ - long getLong(String key, long defaultValue); -} diff --git a/common/src/com/android/tv/common/config/api/RemoteConfigValue.java b/common/src/com/android/tv/common/config/api/RemoteConfigValue.java deleted file mode 100644 index 6da89fb9..00000000 --- a/common/src/com/android/tv/common/config/api/RemoteConfigValue.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2018 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.config.api; - -/** Wrapper for a RemoteConfig key and default value. */ -public abstract class RemoteConfigValue<T> { - private final T defaultValue; - private final String key; - - private RemoteConfigValue(String key, T defaultValue) { - this.defaultValue = defaultValue; - this.key = key; - } - - /** Create with the given key and default value */ - public static RemoteConfigValue<Long> create(String key, long defaultValue) { - return new RemoteConfigValue<Long>(key, defaultValue) { - @Override - public Long get(RemoteConfig remoteConfig) { - return remoteConfig.getLong(key, defaultValue); - } - }; - } - - public abstract T get(RemoteConfig remoteConfig); - - public final T getDefaultValue() { - return defaultValue; - } - - @Override - public final String toString() { - return "RemoteConfigValue(key=" + key + ", defalutValue=" + defaultValue + "]"; - } -} diff --git a/common/src/com/android/tv/common/dagger/ApplicationModule.java b/common/src/com/android/tv/common/dagger/ApplicationModule.java new file mode 100644 index 00000000..4655f777 --- /dev/null +++ b/common/src/com/android/tv/common/dagger/ApplicationModule.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 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.dagger; + +import android.app.Application; +import android.content.ContentResolver; +import android.content.Context; +import android.os.Looper; +import com.android.tv.common.dagger.annotations.ApplicationContext; +import com.android.tv.common.dagger.annotations.MainLooper; +import dagger.Module; +import dagger.Provides; + +/** + * Provides application-scope qualifiers for the {@link Application}, the application context, and + * the application's main looper. + */ +@Module +public final class ApplicationModule { + private final Application mApplication; + + public ApplicationModule(Application application) { + mApplication = application; + } + + @Provides + Application provideApplication() { + return mApplication; + } + + @Provides + @ApplicationContext + Context provideContext() { + return mApplication.getApplicationContext(); + } + + @Provides + @MainLooper + static Looper provideMainLooper() { + return Looper.getMainLooper(); + } + + @Provides + ContentResolver provideContentResolver() { + return mApplication.getContentResolver(); + } +} diff --git a/common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java b/common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java new file mode 100644 index 00000000..86318156 --- /dev/null +++ b/common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 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.dagger.annotations; + +import javax.inject.Qualifier; + +/** Annotation for requesting the application's context. */ +@Qualifier +public @interface ApplicationContext {} diff --git a/common/src/com/android/tv/common/dagger/annotations/MainLooper.java b/common/src/com/android/tv/common/dagger/annotations/MainLooper.java new file mode 100644 index 00000000..a8b4100f --- /dev/null +++ b/common/src/com/android/tv/common/dagger/annotations/MainLooper.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 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.dagger.annotations; + +import javax.inject.Qualifier; + +/** Annotation for requesting a Looper that is on the UI thread. */ +@Qualifier +public @interface MainLooper {} diff --git a/common/src/com/android/tv/common/data/RecordedProgramState.java b/common/src/com/android/tv/common/data/RecordedProgramState.java new file mode 100644 index 00000000..3bfad88d --- /dev/null +++ b/common/src/com/android/tv/common/data/RecordedProgramState.java @@ -0,0 +1,14 @@ +package com.android.tv.common.data; + +/** The recording state. */ +// TODO(b/25023911): Use @SimpleEnum when it is supported by AutoValue +public enum RecordedProgramState { + // TODO(b/71717809): Document each state. + NOT_SET, + STARTED, + FINISHED, + PARTIAL, + FAILED, + DELETE, + DELETED, +} diff --git a/common/src/com/android/tv/common/experiments/ExperimentFlag.java b/common/src/com/android/tv/common/experiments/ExperimentFlag.java index c9bacac4..b8370ad6 100644 --- a/common/src/com/android/tv/common/experiments/ExperimentFlag.java +++ b/common/src/com/android/tv/common/experiments/ExperimentFlag.java @@ -19,41 +19,63 @@ package com.android.tv.common.experiments; import android.support.annotation.VisibleForTesting; import com.android.tv.common.BuildConfig; +import com.google.common.base.Supplier; /** Experiments return values based on user, device and other criteria. */ public final class ExperimentFlag<T> { + // NOTE: sAllowOverrides IS NEVER USED in the non AOSP version. private static boolean sAllowOverrides = false; @VisibleForTesting public static void initForTest() { + /* Begin_AOSP_Comment_Out + if (!BuildConfig.AOSP) { + PhenotypeFlag.initForTest(); + return; + } + End_AOSP_Comment_Out */ sAllowOverrides = true; } /** Returns a boolean experiment */ public static ExperimentFlag<Boolean> createFlag( +// AOSP_Comment_Out Supplier<Boolean> phenotypeFlag, boolean defaultValue) { return new ExperimentFlag<>( +// AOSP_Comment_Out phenotypeFlag, defaultValue); } private final T mDefaultValue; +// AOSP_Comment_Out private final Supplier<T> mPhenotypeFlag; +// AOSP_Comment_Out // NOTE: mOverrideValue IS NEVER USED in the non AOSP version. private T mOverrideValue = null; + // mOverridden IS NEVER USED in the non AOSP version. private boolean mOverridden = false; private ExperimentFlag( +// AOSP_Comment_Out Supplier<T> phenotypeFlag, + // NOTE: defaultValue IS NEVER USED in the non AOSP version. T defaultValue) { mDefaultValue = defaultValue; +// AOSP_Comment_Out mPhenotypeFlag = phenotypeFlag; } /** Returns value for this experiment */ public T get() { + /* Begin_AOSP_Comment_Out + if (!BuildConfig.AOSP) { + return mPhenotypeFlag.get(); + } + End_AOSP_Comment_Out */ return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue; } @VisibleForTesting public void override(T t) { + if (sAllowOverrides) { mOverridden = true; mOverrideValue = t; @@ -64,4 +86,11 @@ public final class ExperimentFlag<T> { public void resetOverride() { mOverridden = false; } + + /* Begin_AOSP_Comment_Out + @VisibleForTesting + T getAospDefaultValueForTesting() { + return mDefaultValue; + } + End_AOSP_Comment_Out */ } diff --git a/common/src/com/android/tv/common/experiments/Experiments.java b/common/src/com/android/tv/common/experiments/Experiments.java index 96b15e53..9bfdb547 100644 --- a/common/src/com/android/tv/common/experiments/Experiments.java +++ b/common/src/com/android/tv/common/experiments/Experiments.java @@ -19,6 +19,7 @@ package com.android.tv.common.experiments; import static com.android.tv.common.experiments.ExperimentFlag.createFlag; import com.android.tv.common.BuildConfig; +// AOSP_Comment_Out import com.android.tv.common.flags.LiveChannels; /** * Set of experiments visible in AOSP. @@ -26,17 +27,15 @@ import com.android.tv.common.BuildConfig; * <p>This file is maintained by hand. */ public final class Experiments { - public static final ExperimentFlag<Boolean> CLOUD_EPG = - ExperimentFlag.createFlag( - true); - public static final ExperimentFlag<Boolean> ENABLE_UNRATED_CONTENT_SETTINGS = ExperimentFlag.createFlag( +// AOSP_Comment_Out LiveChannels::enableUnratedContentSettings, false); /** Turn analytics on or off based on the System Checkbox for logging. */ public static final ExperimentFlag<Boolean> ENABLE_ANALYTICS_VIA_CHECKBOX = createFlag( +// AOSP_Comment_Out LiveChannels::enableAnalyticsViaCheckbox, false); /** @@ -46,6 +45,7 @@ public final class Experiments { */ public static final ExperimentFlag<Boolean> ENABLE_DEVELOPER_FEATURES = ExperimentFlag.createFlag( +// AOSP_Comment_Out LiveChannels::enableDeveloperFeatures, BuildConfig.ENG); /** @@ -57,6 +57,7 @@ public final class Experiments { */ public static final ExperimentFlag<Boolean> ENABLE_QA_FEATURES = ExperimentFlag.createFlag( +// AOSP_Comment_Out LiveChannels::enableQaFeatures, false); private Experiments() {} diff --git a/common/src/com/android/tv/common/feature/EngOnlyFeature.java b/common/src/com/android/tv/common/feature/BuildTypeFeature.java index 5feb5481..9e1704e8 100644 --- a/common/src/com/android/tv/common/feature/EngOnlyFeature.java +++ b/common/src/com/android/tv/common/feature/BuildTypeFeature.java @@ -20,18 +20,23 @@ import android.content.Context; import com.android.tv.common.BuildConfig; /** A feature that is only available on {@link BuildConfig#ENG} builds. */ -public final class EngOnlyFeature implements Feature { - public static final Feature ENG_ONLY_FEATURE = new EngOnlyFeature(); +public final class BuildTypeFeature implements Feature { + public static final Feature ENG_ONLY_FEATURE = new BuildTypeFeature(BuildConfig.ENG); + public static final Feature ASOP_FEATURE = new BuildTypeFeature(BuildConfig.AOSP); - private EngOnlyFeature() {} + private final boolean mIsBuildType; + + private BuildTypeFeature(boolean isBuildType) { + mIsBuildType = isBuildType; + } @Override public boolean isEnabled(Context context) { - return BuildConfig.ENG; + return mIsBuildType; } @Override public String toString() { - return "EngOnlyFeature(" + BuildConfig.ENG + ")"; + return getClass().getSimpleName() + "(" + mIsBuildType + ")"; } } diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java index 1fceabb3..04052a7c 100644 --- a/common/src/com/android/tv/common/feature/CommonFeatures.java +++ b/common/src/com/android/tv/common/feature/CommonFeatures.java @@ -16,18 +16,16 @@ package com.android.tv.common.feature; -import static com.android.tv.common.feature.FeatureUtils.AND; +import static com.android.tv.common.feature.BuildTypeFeature.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; import android.content.Context; -import android.os.Build; -import android.text.TextUtils; import android.util.Log; -import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig; -import com.android.tv.common.experiments.Experiments; - -import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.flags.has.HasCloudEpgFlags; import com.android.tv.common.util.LocationUtils; +import com.android.tv.common.flags.CloudEpgFlags; /** * List of {@link Feature} that affect more than just the Live TV app. @@ -46,7 +44,7 @@ public class CommonFeatures { * <p>DVR API is introduced in N, it only works when app runs as a system app. */ public static final TestableFeature DVR = - createTestableFeature(AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE)); + createTestableFeature(and(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE)); /** * ENABLE_RECORDING_REGARDLESS_OF_STORAGE_STATUS @@ -56,44 +54,32 @@ public class CommonFeatures { public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE = PropertyFeature.create("force_recording_until_no_space", false); - public static final Feature TUNER = - new Feature() { - @Override - public boolean isEnabled(Context context) { - - if (CommonUtils.isDeveloper()) { - // we enable tuner for developers to test tuner in any platform. - return true; - } - - // This is special handling just for USB Tuner. - // It does not require any N API's but relies on a improvements in N for AC3 - // support - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; - } - }; - /** Show postal code fragment before channel scan. */ public static final Feature ENABLE_CLOUD_EPG_REGION = - new Feature() { - private final String[] supportedRegions = { - }; + or( + FlagFeature.from(HasCloudEpgFlags::fromContext, CloudEpgFlags::supportedRegion), + new Feature() { + private final String[] supportedRegions = { +// AOSP_Comment_Out "US", "GB" + }; - - @Override - public boolean isEnabled(Context context) { - if (!Experiments.CLOUD_EPG.get()) { - if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false"); - return false; - } - String country = LocationUtils.getCurrentCountry(context); - for (int i = 0; i < supportedRegions.length; i++) { - if (supportedRegions[i].equalsIgnoreCase(country)) { - return true; + @Override + public boolean isEnabled(Context context) { + String country = LocationUtils.getCurrentCountry(context); + for (int i = 0; i < supportedRegions.length; i++) { + if (supportedRegions[i].equalsIgnoreCase(country)) { + return true; + } + } + if (DEBUG) Log.d(TAG, "EPG flag false after country check"); + return false; } - } - if (DEBUG) Log.d(TAG, "EPG flag false after country check"); - return false; - } - }; + }); + + // TODO(b/74197177): remove when UI and API finalized. + /** Show channel signal strength. */ + public static final Feature TUNER_SIGNAL_STRENGTH = ENG_ONLY_FEATURE; + + /** Use AudioOnlyTvService for audio-only inputs. */ + public static final Feature ENABLE_TV_SERVICE = ENG_ONLY_FEATURE; } diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java index 8650d151..aaed6c82 100644 --- a/common/src/com/android/tv/common/feature/FeatureUtils.java +++ b/common/src/com/android/tv/common/feature/FeatureUtils.java @@ -17,6 +17,7 @@ package com.android.tv.common.feature; import android.content.Context; +import com.android.tv.common.BuildConfig; import com.android.tv.common.util.CommonUtils; import java.util.Arrays; @@ -28,7 +29,7 @@ public class FeatureUtils { * * @param features the features to or */ - public static Feature OR(final Feature... features) { + public static Feature or(final Feature... features) { return new Feature() { @Override public boolean isEnabled(Context context) { @@ -52,7 +53,7 @@ public class FeatureUtils { * * @param features the features to and */ - public static Feature AND(final Feature... features) { + public static Feature and(final Feature... features) { return new Feature() { @Override public boolean isEnabled(Context context) { @@ -70,6 +71,42 @@ public class FeatureUtils { } }; } + /** + * A feature available in AOSP. + * + * @param googleFeature the feature used in non AOSP builds + * @param aospFeature the feature used in AOSP builds + */ + public static Feature aospFeature( +// AOSP_Comment_Out final Feature googleFeature, + final Feature aospFeature) { + /* Begin_AOSP_Comment_Out + if (!BuildConfig.AOSP) { + return googleFeature; + } else { + End_AOSP_Comment_Out */ + return aospFeature; +// AOSP_Comment_Out } + } + + /** + * Returns a feature that is opposite of the given {@code feature}. + * + * @param feature the feature to invert + */ + public static Feature not(final Feature feature) { + return new Feature() { + @Override + public boolean isEnabled(Context context) { + return !feature.isEnabled(context); + } + + @Override + public String toString() { + return "not(" + feature + ")"; + } + }; + } /** A feature that is always enabled. */ public static final Feature ON = diff --git a/common/src/com/android/tv/common/feature/GServiceFeature.java b/common/src/com/android/tv/common/feature/FlagFeature.java index 1d7d1156..8da470ef 100644 --- a/common/src/com/android/tv/common/feature/GServiceFeature.java +++ b/common/src/com/android/tv/common/feature/FlagFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 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. @@ -13,31 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License */ - package com.android.tv.common.feature; import android.content.Context; +import com.google.common.base.Function; + +/** Feature from a Flag */ +public class FlagFeature<T> implements Feature { -/** A feature controlled by a GServices flag. */ -public class GServiceFeature implements Feature { - private static final String LIVECHANNELS_PREFIX = "livechannels:"; - private final String mKey; - private final boolean mDefaultValue; + private final Function<Context, T> mToFlag; + private final Function<T, Boolean> mToBoolean; - public GServiceFeature(String key, boolean defaultValue) { - mKey = LIVECHANNELS_PREFIX + key; - mDefaultValue = defaultValue; + public static <T> FlagFeature<T> from( + Function<Context, T> toFlag, Function<T, Boolean> toBoolean) { + return new FlagFeature<T>(toFlag, toBoolean); + } + + private FlagFeature(Function<Context, T> toFlag, Function<T, Boolean> toBoolean) { + mToFlag = toFlag; + mToBoolean = toBoolean; } @Override public boolean isEnabled(Context context) { - - // GServices is not available outside of Google. - return mDefaultValue; + return mToBoolean.apply(mToFlag.apply(context)); } @Override public String toString() { - return "GService[hash=" + mKey.hashCode() + "]"; + return mToBoolean.toString(); } } diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java index 155b391d..4b0a925f 100644 --- a/common/src/com/android/tv/common/feature/Sdk.java +++ b/common/src/com/android/tv/common/feature/Sdk.java @@ -17,25 +17,33 @@ package com.android.tv.common.feature; import android.content.Context; -import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; /** Holder for SDK version features */ public final class Sdk { - public static final Feature AT_LEAST_N = - new Feature() { - @Override - public boolean isEnabled(Context context) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; - } - }; - - public static final Feature AT_LEAST_O = - new Feature() { - @Override - public boolean isEnabled(Context context) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - } - }; + + public static final Feature AT_LEAST_M = new AtLeast(VERSION_CODES.M); + + public static final Feature AT_LEAST_N = new AtLeast(VERSION_CODES.N); + + public static final Feature AT_LEAST_O = new AtLeast(VERSION_CODES.O); + + public static final Feature AT_LEAST_P = new AtLeast(VERSION_CODES.P); // AOSP_OC:strip_line + + private static final class AtLeast implements Feature { + + private final int versionCode; + + private AtLeast(int versionCode) { + this.versionCode = versionCode; + } + + @Override + public boolean isEnabled(Context unused) { + return VERSION.SDK_INT >= versionCode; + } + } private Sdk() {} } diff --git a/common/src/com/android/tv/common/flags/BackendKnobsFlags.java b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java new file mode 100644 index 00000000..69bac7a0 --- /dev/null +++ b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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.flags; + +/** Flags for tuning non ui behavior */ +public interface BackendKnobsFlags { + + /** + * Whether or not this feature is compiled into this build. + * + * <p>This returns true by default, unless the is_compiled_selector parameter was set during + * code generation. + */ + boolean compiled(); + + /** Enable fetching only part of the program data. */ + boolean enablePartialProgramFetch(); + + /** EPG fetcher interval in hours */ + long epgFetcherIntervalHour(); + + /** Target channel count for EPG. It is used to adjust the EPG length */ + long epgTargetChannelCount(); + + /** Enables fetching a few hours of programs only when the epg is scrolled to that time. */ + boolean fetchProgramsAsNeeded(); + + /** How many hours of programs are loaded in the program guide for during the initial fetch */ + long programGuideInitialFetchHours(); + + /** How many hours of programs are loaded in the program guide */ + long programGuideMaxHours(); +} diff --git a/common/src/com/android/tv/common/flags/CloudEpgFlags.java b/common/src/com/android/tv/common/flags/CloudEpgFlags.java new file mode 100755 index 00000000..ab4c6a17 --- /dev/null +++ b/common/src/com/android/tv/common/flags/CloudEpgFlags.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 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.flags; + +/** Flags for Cloud EPG */ +public interface CloudEpgFlags { + + /** + * Whether or not this feature is compiled into this build. + * + * <p>This returns true by default, unless the is_compiled_selector parameter was set during + * code generation. + */ + boolean compiled(); + + /** Is the device in a region supported by Cloud Epg */ + boolean supportedRegion(); + + /** List of input ids that Live TV will update their EPG. */ + String thirdPartyEpgInputsCsv(); +} diff --git a/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java new file mode 100755 index 00000000..1afff793 --- /dev/null +++ b/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 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.flags; + +/** Flags allowing concurrent DVR playback */ +public interface ConcurrentDvrPlaybackFlags { + + /** + * Whether or not this feature is compiled into this build. + * + * <p>This returns true by default, unless the is_compiled_selector parameter was set during + * code generation. + */ + boolean compiled(); + + /** Enable playback of DVR playback during recording */ + boolean enabled(); + + /** Enable tuner using recording data for playback in onTune */ + boolean onTuneUsesRecording(); +} diff --git a/common/src/com/android/tv/common/flags/TunerFlags.java b/common/src/com/android/tv/common/flags/TunerFlags.java new file mode 100755 index 00000000..5f899b90 --- /dev/null +++ b/common/src/com/android/tv/common/flags/TunerFlags.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 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.flags; + +/** Flags for tuner */ +public interface TunerFlags { + + /** + * Whether or not this feature is compiled into this build. + * + * <p>This returns true by default, unless the is_compiled_selector parameter was set during + * code generation. + */ + boolean compiled(); + + /** Tune using current recording if available. */ + boolean tuneUsingRecording(); + + /** Enable using exoplayer V2 */ + boolean useExoplayerV2(); +} diff --git a/common/src/com/android/tv/common/flags/UiFlags.java b/common/src/com/android/tv/common/flags/UiFlags.java new file mode 100755 index 00000000..4c88d08a --- /dev/null +++ b/common/src/com/android/tv/common/flags/UiFlags.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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.flags; + +/** Flags for Live TV UI */ +public interface UiFlags { + + /** + * Whether or not this feature is compiled into this build. + * + * <p>This returns true by default, unless the is_compiled_selector parameter was set during + * code generation. + */ + boolean compiled(); + + /** + * Number of days to be shown by Recording History. + * + * <p>Set to 0 for all recordings. + */ + long maxHistoryDays(); + + /** Unhide the launcher all the time */ + boolean uhideLauncher(); + + /** Use the Leanback Pin Picker */ + boolean useLeanbackPinPicker(); +} diff --git a/common/src/com/android/tv/common/flags/has/HasCloudEpgFlags.java b/common/src/com/android/tv/common/flags/has/HasCloudEpgFlags.java new file mode 100644 index 00000000..c33c5528 --- /dev/null +++ b/common/src/com/android/tv/common/flags/has/HasCloudEpgFlags.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.flags.has; + +import android.content.Context; +import com.android.tv.common.flags.CloudEpgFlags; + +/** Has {@link CloudEpgFlags} */ +public interface HasCloudEpgFlags { + + static CloudEpgFlags fromContext(Context context) { + return ((HasCloudEpgFlags) HasUtils.getApplicationContext(context)).getCloudEpgFlags(); + } + + CloudEpgFlags getCloudEpgFlags(); +} diff --git a/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java new file mode 100644 index 00000000..b4710875 --- /dev/null +++ b/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.flags.has; + +import android.content.Context; +import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags; + +/** Has {@link ConcurrentDvrPlaybackFlags} */ +public interface HasConcurrentDvrPlaybackFlags { + + static ConcurrentDvrPlaybackFlags fromContext(Context context) { + return ((HasConcurrentDvrPlaybackFlags) HasUtils.getApplicationContext(context)) + .getConcurrentDvrPlaybackFlags(); + } + + ConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags(); +} diff --git a/common/src/com/android/tv/common/flags/has/HasUiFlags.java b/common/src/com/android/tv/common/flags/has/HasUiFlags.java new file mode 100644 index 00000000..72cc84f2 --- /dev/null +++ b/common/src/com/android/tv/common/flags/has/HasUiFlags.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.flags.has; + +import com.android.tv.common.flags.UiFlags; + +/** Has {@link UiFlags} */ +public interface HasUiFlags { + + UiFlags getUiFlags(); +} diff --git a/common/src/com/android/tv/common/flags/has/HasUtils.java b/common/src/com/android/tv/common/flags/has/HasUtils.java new file mode 100644 index 00000000..1c6126dc --- /dev/null +++ b/common/src/com/android/tv/common/flags/has/HasUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.flags.has; + +import android.content.Context; + +/** Static utilities for Has interfaces. */ +public final class HasUtils { + + /** Returns the application context. */ + public static Context getApplicationContext(Context context) { + Context appContext = context.getApplicationContext(); + return appContext != null ? appContext : context; + } + + private HasUtils() {} +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java new file mode 100644 index 00000000..a189e473 --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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.flags.impl; + +/** Flags for tuning non ui behavior. */ +public final class DefaultBackendKnobsFlags + implements com.android.tv.common.flags.BackendKnobsFlags { + + @Override + public boolean compiled() { + return true; + } + + @Override + public boolean enablePartialProgramFetch() { + return false; + } + + @Override + public long epgFetcherIntervalHour() { + return 25; + } + + @Override + public boolean fetchProgramsAsNeeded() { + return false; + } + + @Override + public long programGuideInitialFetchHours() { + return 8; + } + + @Override + public long programGuideMaxHours() { + return 336; + } + + @Override + public long epgTargetChannelCount() { + return 100; + } +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.java new file mode 100644 index 00000000..34c4fc4b --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.flags.impl; + +import com.android.tv.common.flags.CloudEpgFlags; + +/** Default flags for Cloud EPG */ +public final class DefaultCloudEpgFlags implements CloudEpgFlags { + + private String mThirdPartyEpgInputCsv = + "com.google.android.tv/.tuner.tvinput.TunerTvInputService," + + "com.technicolor.skipper.tuner/.tvinput.TunerTvInputService," + + "com.silicondust.view/.tif.SDTvInputService"; + + @Override + public boolean compiled() { + return true; + } + + @Override + public boolean supportedRegion() { + return false; + } + + public void setThirdPartyEpgInputCsv(String value) { + mThirdPartyEpgInputCsv = value; + } + + @Override + public String thirdPartyEpgInputsCsv() { + return mThirdPartyEpgInputCsv; + } +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java new file mode 100644 index 00000000..8d8c584a --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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.flags.impl; + +import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags; + +/** Default flags for Concurrent DVR Playback */ +public final class DefaultConcurrentDvrPlaybackFlags implements ConcurrentDvrPlaybackFlags { + + @Override + public boolean compiled() { + return true; + } + + @Override + public boolean enabled() { + return false; + } + + @Override + public boolean onTuneUsesRecording() { + return false; + } +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java b/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java new file mode 100644 index 00000000..49352364 --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 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.flags.impl; + +import dagger.Module; +import dagger.Provides; +import dagger.Reusable; +import com.android.tv.common.flags.BackendKnobsFlags; +import com.android.tv.common.flags.CloudEpgFlags; +import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags; +import com.android.tv.common.flags.TunerFlags; +import com.android.tv.common.flags.UiFlags; + +/** Provides default flags. */ +@Module +public class DefaultFlagsModule { + + @Provides + @Reusable + BackendKnobsFlags provideBackendKnobsFlags() { + return new DefaultBackendKnobsFlags(); + } + + @Provides + @Reusable + CloudEpgFlags provideCloudEpgFlags() { + return new DefaultCloudEpgFlags(); + } + + @Provides + @Reusable + ConcurrentDvrPlaybackFlags provideConcurrentDvrPlaybackFlags() { + return new DefaultConcurrentDvrPlaybackFlags(); + } + + @Provides + @Reusable + TunerFlags provideTunerFlags() { + return new DefaultTunerFlags(); + } + + @Provides + @Reusable + UiFlags provideUiFlags() { + return new DefaultUiFlags(); + } +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java new file mode 100644 index 00000000..195953bc --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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.flags.impl; + +import com.android.tv.common.flags.TunerFlags; + +/** Default Flags for Tuner */ +public class DefaultTunerFlags implements TunerFlags { + + @Override + public boolean compiled() { + return true; + } + + @Override + public boolean tuneUsingRecording() { + return false; + } + + @Override + public boolean useExoplayerV2() { + return false; + } +} diff --git a/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java new file mode 100644 index 00000000..fce45853 --- /dev/null +++ b/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 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.flags.impl; + +import com.android.tv.common.flags.UiFlags; + +/** Default Flags for Live TV UI */ +public class DefaultUiFlags implements UiFlags { + + @Override + public boolean compiled() { + return true; + } + + @Override + public boolean uhideLauncher() { + return false; + } + + @Override + public boolean useLeanbackPinPicker() { + return false; + } + + @Override + public long maxHistoryDays() { + return 7; + } +} diff --git a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java index 8b45a730..0fb864bd 100644 --- a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java +++ b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java @@ -217,6 +217,7 @@ public class RecordingStorageStatusManager { } } catch (IllegalArgumentException e) { // In rare cases, storage status change was not notified yet. + Log.w(TAG, "Error getting Dvr Storage Status.", e); SoftPreconditions.checkState(false); return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; } @@ -246,7 +247,7 @@ public class RecordingStorageStatusManager { StatFs statFs = new StatFs(storageMountedDir.toString()); storageMountedCapacity = statFs.getTotalBytes(); } catch (IllegalArgumentException e) { - Log.e(TAG, "Storage mount status was changed."); + Log.w(TAG, "Storage mount status was changed.", e); storageMounted = false; storageMountedDir = null; } diff --git a/common/src/com/android/tv/common/singletons/HasSingletons.java b/common/src/com/android/tv/common/singletons/HasSingletons.java new file mode 100644 index 00000000..193aed3a --- /dev/null +++ b/common/src/com/android/tv/common/singletons/HasSingletons.java @@ -0,0 +1,33 @@ +/* + * Copyright 2018 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.singletons; + +import android.content.Context; + +/** + * A type that can know about and supply a singleton, typically a type t such as an android activity + * or application. + */ +public interface HasSingletons<C> { + + @SuppressWarnings("unchecked") // injection + static <C> C get(Class<C> clazz, Context context) { + return ((HasSingletons<C>) context).singletons(); + } + + /** Returns the strongly typed singleton. */ + C singletons(); +} diff --git a/common/src/com/android/tv/common/singletons/HasTvInputId.java b/common/src/com/android/tv/common/singletons/HasTvInputId.java new file mode 100644 index 00000000..4bc0a21c --- /dev/null +++ b/common/src/com/android/tv/common/singletons/HasTvInputId.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.singletons; + +/** + * Has TunerInputId. + * + * <p>This is used buy both the tuner to get its input id and by the Live TV to get the + * embedded tuner input id. + */ +public interface HasTvInputId { + + String getEmbeddedTunerInputId(); +} diff --git a/common/src/com/android/tv/common/support/README.md b/common/src/com/android/tv/common/support/README.md new file mode 100644 index 00000000..67993f37 --- /dev/null +++ b/common/src/com/android/tv/common/support/README.md @@ -0,0 +1,8 @@ +# Support Libraries + +Packages here are destined to become support libraries. + +Each package should be self contained and only have dependencies on public libraries. + +It if becomes clear a package should not or will not be part of a support library move it to a +different location.
\ No newline at end of file diff --git a/common/src/com/android/tv/common/support/tis/BaseTvInputService.java b/common/src/com/android/tv/common/support/tis/BaseTvInputService.java new file mode 100644 index 00000000..7791550b --- /dev/null +++ b/common/src/com/android/tv/common/support/tis/BaseTvInputService.java @@ -0,0 +1,90 @@ +/* + * Copyright 2018 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.support.tis; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputService; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import com.android.tv.common.support.tis.TifSession.TifSessionFactory; + +/** Abstract TVInputService. */ +public abstract class BaseTvInputService extends TvInputService { + + private static final IntentFilter INTENT_FILTER = new IntentFilter(); + + static { + INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); + INTENT_FILTER.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED); + } + + @VisibleForTesting + protected final BroadcastReceiver broadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED: + case TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED: + for (Session session : getSessionManager().getSessions()) { + if (session instanceof WrappedSession) { + ((WrappedSession) session).onParentalControlsChanged(); + } + } + break; + default: + // do nothing + } + } + }; + + @Nullable + @Override + public final WrappedSession onCreateSession(String inputId) { + SessionManager sessionManager = getSessionManager(); + if (sessionManager.canCreateNewSession()) { + WrappedSession session = + new WrappedSession( + getApplicationContext(), + sessionManager, + getTifSessionFactory(), + inputId); + sessionManager.addSession(session); + return session; + } + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + registerReceiver(broadcastReceiver, INTENT_FILTER); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(broadcastReceiver); + } + + protected abstract TifSessionFactory getTifSessionFactory(); + + protected abstract SessionManager getSessionManager(); +} diff --git a/common/src/com/android/tv/common/support/tis/SessionManager.java b/common/src/com/android/tv/common/support/tis/SessionManager.java new file mode 100644 index 00000000..5eeebc80 --- /dev/null +++ b/common/src/com/android/tv/common/support/tis/SessionManager.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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.support.tis; + +import android.media.tv.TvInputService.Session; +import com.google.common.collect.ImmutableSet; + +/** Manages the number of concurrent sessions, keeping track of when sessions are released. */ +public interface SessionManager { + + void removeSession(Session session); + + void addSession(Session session); + + boolean canCreateNewSession(); + + ImmutableSet<Session> getSessions(); +} diff --git a/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java b/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java new file mode 100644 index 00000000..f0636ccc --- /dev/null +++ b/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 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.support.tis; + +import android.media.tv.TvInputService.Session; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.support.annotation.VisibleForTesting; +import android.util.ArraySet; +import com.google.common.collect.ImmutableSet; +import java.util.HashSet; +import java.util.Set; + +/** A simple session manager that allows a maximum number of concurrent session. */ +public final class SimpleSessionManager implements SessionManager { + + private final Set<Session> sessions; + private final int max; + + public SimpleSessionManager(int max) { + this.max = max; + sessions = VERSION.SDK_INT >= VERSION_CODES.M ? new ArraySet<>() : new HashSet<>(); + } + + @Override + public void removeSession(Session session) { + sessions.remove(session); + } + + @Override + public void addSession(Session session) { + sessions.add(session); + } + + @Override + public boolean canCreateNewSession() { + return sessions.size() < max; + } + + @Override + public ImmutableSet<Session> getSessions() { + return ImmutableSet.copyOf(sessions); + } + + @VisibleForTesting + int getSessionCount() { + return sessions.size(); + } +} diff --git a/common/src/com/android/tv/common/support/tis/TifSession.java b/common/src/com/android/tv/common/support/tis/TifSession.java new file mode 100644 index 00000000..61cfe767 --- /dev/null +++ b/common/src/com/android/tv/common/support/tis/TifSession.java @@ -0,0 +1,203 @@ +/* + * Copyright 2018 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.support.tis; + +import android.annotation.TargetApi; +import android.media.PlaybackParams; +import android.media.tv.TvContentRating; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputService.Session; +import android.media.tv.TvTrackInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.support.annotation.FloatRange; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Surface; +import android.view.View; +import java.util.List; + +/** + * Custom {@link android.media.tv.TvInputService.Session} class that uses delegation and a callback + * to separate it from the TvInputService for easier testing. + */ +public abstract class TifSession { + + private final TifSessionCallbacks callback; + + /** + * Creates TV Input Framework Session with the given callback. + * + * <p>The callback is used to pass notification to the actual {@link + * android.media.tv.TvInputService.Session}. + * + * <p>Pass a mock callback for tests. + */ + protected TifSession(TifSessionCallbacks callback) { + this.callback = callback; + } + + /** + * Called after this session had been created and the callback is attached. + * + * <p>Do not call notify methods in the constructor, instead call them here if needed at + * creation time. eg @{@link Session#notifyTimeShiftStatusChanged(int)}. + */ + public void onSessionCreated() {} + + /** @see Session#onRelease() */ + public void onRelease() {} + + /** @see Session#onSetSurface(Surface) */ + public abstract boolean onSetSurface(@Nullable Surface surface); + + /** @see Session#onSurfaceChanged(int, int, int) */ + public abstract void onSurfaceChanged(int format, int width, int height); + + /** @see Session#onSetStreamVolume(float) */ + public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume); + + /** @see Session#onTune(Uri) */ + public abstract boolean onTune(Uri channelUri); + + /** @see Session#onSetCaptionEnabled(boolean) */ + public abstract void onSetCaptionEnabled(boolean enabled); + + /** @see Session#onUnblockContent(TvContentRating) */ + public abstract void onUnblockContent(TvContentRating unblockedRating); + + /** @see Session#onTimeShiftGetCurrentPosition() */ + @TargetApi(Build.VERSION_CODES.M) + public long onTimeShiftGetCurrentPosition() { + return TvInputManager.TIME_SHIFT_INVALID_TIME; + } + + /** @see Session#onTimeShiftGetStartPosition() */ + @TargetApi(Build.VERSION_CODES.M) + public long onTimeShiftGetStartPosition() { + return TvInputManager.TIME_SHIFT_INVALID_TIME; + } + + /** @see Session#onTimeShiftPause() */ + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftPause() {} + + /** @see Session#onTimeShiftResume() */ + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftResume() {} + + /** @see Session#onTimeShiftSeekTo(long) */ + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftSeekTo(long timeMs) {} + + /** @see Session#onTimeShiftSetPlaybackParams(PlaybackParams) */ + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftSetPlaybackParams(PlaybackParams params) {} + + public void onParentalControlsChanged() {} + + /** @see Session#notifyChannelRetuned(Uri) */ + public final void notifyChannelRetuned(final Uri channelUri) { + callback.notifyChannelRetuned(channelUri); + } + + /** @see Session#notifyTracksChanged(List) */ + public final void notifyTracksChanged(final List<TvTrackInfo> tracks) { + callback.notifyTracksChanged(tracks); + } + + /** @see Session#notifyTrackSelected(int, String) */ + public final void notifyTrackSelected(final int type, final String trackId) { + callback.notifyTrackSelected(type, trackId); + } + + /** @see Session#notifyVideoAvailable() */ + public final void notifyVideoAvailable() { + callback.notifyVideoAvailable(); + } + + /** @see Session#notifyVideoUnavailable(int) */ + public final void notifyVideoUnavailable(final int reason) { + callback.notifyVideoUnavailable(reason); + } + + /** @see Session#notifyContentAllowed() */ + public final void notifyContentAllowed() { + callback.notifyContentAllowed(); + } + + /** @see Session#notifyContentBlocked(TvContentRating) */ + public final void notifyContentBlocked(@NonNull final TvContentRating rating) { + callback.notifyContentBlocked(rating); + } + + /** @see Session#notifyTimeShiftStatusChanged(int) */ + @TargetApi(VERSION_CODES.M) + public final void notifyTimeShiftStatusChanged(final int status) { + callback.notifyTimeShiftStatusChanged(status); + } + + /** @see Session#setOverlayViewEnabled(boolean) */ + public void setOverlayViewEnabled(boolean enabled) { + callback.setOverlayViewEnabled(enabled); + } + + /** @see Session#onCreateOverlayView() */ + public View onCreateOverlayView() { + return null; + } + + /** @see Session#onOverlayViewSizeChanged(int, int) */ + public void onOverlayViewSizeChanged(int width, int height) {} + + /** + * Callbacks used to notify the {@link android.media.tv.TvInputService.Session}. + * + * <p>This is implemented internally by {@link WrappedSession}, and can be mocked for tests. + */ + public interface TifSessionCallbacks { + /** @see Session#notifyChannelRetuned(Uri) */ + void notifyChannelRetuned(final Uri channelUri); + /** @see Session#notifyTracksChanged(List) */ + void notifyTracksChanged(final List<TvTrackInfo> tracks); + /** @see Session#notifyTrackSelected(int, String) */ + void notifyTrackSelected(final int type, final String trackId); + /** @see Session#notifyVideoAvailable() */ + void notifyVideoAvailable(); + /** @see Session#notifyVideoUnavailable(int) */ + void notifyVideoUnavailable(final int reason); + /** @see Session#notifyContentAllowed() */ + void notifyContentAllowed(); + /** @see Session#notifyContentBlocked(TvContentRating) */ + void notifyContentBlocked(@NonNull final TvContentRating rating); + /** @see Session#notifyTimeShiftStatusChanged(int) */ + @TargetApi(VERSION_CODES.M) + void notifyTimeShiftStatusChanged(final int status); + /** @see Session#setOverlayViewEnabled(boolean) */ + void setOverlayViewEnabled(boolean enabled); + } + + /** + * Creates a {@link TifSession}. + * + * <p>This is used by {@link WrappedSession} to create the desired {@code TifSession}. Should be + * used with <a href="http://go/autofactory">go/autofactory</a>. + */ + public interface TifSessionFactory { + TifSession create(TifSessionCallbacks callbacks, String inputId); + } +} diff --git a/common/src/com/android/tv/common/support/tis/WrappedSession.java b/common/src/com/android/tv/common/support/tis/WrappedSession.java new file mode 100644 index 00000000..f4a71dda --- /dev/null +++ b/common/src/com/android/tv/common/support/tis/WrappedSession.java @@ -0,0 +1,148 @@ +/* + * Copyright 2018 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.support.tis; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.PlaybackParams; +import android.media.tv.TvContentRating; +import android.media.tv.TvInputService.Session; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.FloatRange; +import android.support.annotation.Nullable; +import android.view.Surface; +import android.view.View; +import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks; +import com.android.tv.common.support.tis.TifSession.TifSessionFactory; + +/** + * Delegates all call to a {@link TifSession} and removes the session from the {@link + * SessionManager} when {@link Session#onRelease()} is called. + */ +final class WrappedSession extends Session implements TifSessionCallbacks { + + private final SessionManager listener; + private final TifSession delegate; + + WrappedSession( + Context context, + SessionManager sessionManager, + TifSessionFactory sessionFactory, + String inputId) { + super(context); + this.listener = sessionManager; + this.delegate = sessionFactory.create(this, inputId); + } + + @Override + public void onRelease() { + delegate.onRelease(); + listener.removeSession(this); + } + + @Override + public boolean onSetSurface(@Nullable Surface surface) { + return delegate.onSetSurface(surface); + } + + @Override + public void onSurfaceChanged(int format, int width, int height) { + delegate.onSurfaceChanged(format, width, height); + } + + @Override + public void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) { + delegate.onSetStreamVolume(volume); + } + + @Override + public boolean onTune(Uri channelUri) { + return delegate.onTune(channelUri); + } + + @Override + public void onSetCaptionEnabled(boolean enabled) { + delegate.onSetCaptionEnabled(enabled); + } + + @Override + public void onUnblockContent(TvContentRating unblockedRating) { + delegate.onUnblockContent(unblockedRating); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public long onTimeShiftGetCurrentPosition() { + return delegate.onTimeShiftGetCurrentPosition(); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public long onTimeShiftGetStartPosition() { + return delegate.onTimeShiftGetStartPosition(); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftPause() { + delegate.onTimeShiftPause(); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftResume() { + delegate.onTimeShiftResume(); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftSeekTo(long timeMs) { + delegate.onTimeShiftSeekTo(timeMs); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public void onTimeShiftSetPlaybackParams(PlaybackParams params) { + delegate.onTimeShiftSetPlaybackParams(params); + } + + public void onParentalControlsChanged() { + delegate.onParentalControlsChanged(); + } + + @Override + @TargetApi(Build.VERSION_CODES.M) + public void notifyTimeShiftStatusChanged(int status) { + // TODO(nchalko): why is the required for call from TisSession.onSessionCreated to work + super.notifyTimeShiftStatusChanged(status); + } + + @Override + public void setOverlayViewEnabled(boolean enabled) { + super.setOverlayViewEnabled(enabled); + } + + @Override + public View onCreateOverlayView() { + return delegate.onCreateOverlayView(); + } + + @Override + public void onOverlayViewSizeChanged(int width, int height) { + delegate.onOverlayViewSizeChanged(width, height); + } +} diff --git a/common/src/com/android/tv/common/ui/setup/SetupActivity.java b/common/src/com/android/tv/common/ui/setup/SetupActivity.java index 67418ce0..1a3ddbda 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupActivity.java +++ b/common/src/com/android/tv/common/ui/setup/SetupActivity.java @@ -16,7 +16,6 @@ package com.android.tv.common.ui.setup; -import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.os.Bundle; @@ -27,10 +26,10 @@ import android.support.annotation.NonNull; import android.transition.Transition; import android.transition.TransitionInflater; import android.view.View; -import android.view.ViewTreeObserver.OnPreDrawListener; import com.android.tv.common.R; import com.android.tv.common.WeakHandler; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; +import dagger.android.DaggerActivity; /** * Setup activity for onboarding screens or TIS. @@ -38,7 +37,7 @@ import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; * <p>The inherited class should add theme {@code Theme.Setup.GuidedStep} to its definition in * AndroidManifest.xml. */ -public abstract class SetupActivity extends Activity implements OnActionClickListener { +public abstract class SetupActivity extends DaggerActivity implements OnActionClickListener { private static final int MSG_EXECUTE_ACTION = 1; private boolean mShowInitialFragment = true; @@ -55,23 +54,7 @@ public abstract class SetupActivity extends Activity implements OnActionClickLis // Show initial fragment only when the saved state is not restored, because the last // fragment is restored if savesInstanceState is not null. if (savedInstanceState == null) { - // This is the workaround to show the first fragment with delay to show the fragment - // enter transition. See http://b/26255145 - getWindow() - .getDecorView() - .getViewTreeObserver() - .addOnPreDrawListener( - new OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getWindow() - .getDecorView() - .getViewTreeObserver() - .removeOnPreDrawListener(this); - showInitialFragment(); - return true; - } - }); + showInitialFragment(); } else { mShowInitialFragment = false; } diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java index 305431d3..4513a879 100644 --- a/common/src/com/android/tv/common/util/CommonUtils.java +++ b/common/src/com/android/tv/common/util/CommonUtils.java @@ -138,14 +138,23 @@ public final class CommonUtils { return ISO_8601.get().format(new Date(timeMillis)); } - /** Deletes a file or a directory. */ - public static void deleteDirOrFile(File fileOrDirectory) { + /** + * Deletes a file or a directory. + * + * @return <code>true</code> if and only if the file or directory is successfully deleted; + * <code>false</code> otherwise + */ + public static boolean deleteDirOrFile(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) { - for (File child : fileOrDirectory.listFiles()) { - deleteDirOrFile(child); + File[] files = fileOrDirectory.listFiles(); + if (files != null) { + for (File child : files) { + deleteDirOrFile(child); + } } } - fileOrDirectory.delete(); + // If earlier deletes failed this will also + return fileOrDirectory.delete(); } public static boolean isRoboTest() { diff --git a/common/src/com/android/tv/common/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java index 53155298..ee5119eb 100644 --- a/common/src/com/android/tv/common/util/LocationUtils.java +++ b/common/src/com/android/tv/common/util/LocationUtils.java @@ -34,14 +34,20 @@ import com.android.tv.common.BuildConfig; import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; /** A utility class to get the current location. */ public class LocationUtils { private static final String TAG = "LocationUtils"; private static final boolean DEBUG = false; + private static final Set<OnUpdateAddressListener> sOnUpdateAddressListeners = + Collections.synchronizedSet(new HashSet<>()); + private static Context sApplicationContext; private static Address sAddress; private static String sCountry; @@ -63,6 +69,39 @@ public class LocationUtils { return null; } + /** The listener used when address is updated. */ + public interface OnUpdateAddressListener { + /** + * Called when address is updated. + * + * This listener is removed when this method returns true. + * + * @return {@code true} if the job has been finished and the listener needs to be removed; + * {@code false} otherwise. + */ + boolean onUpdateAddress(Address address); + } + + /** + * Add an {@link OnUpdateAddressListener} instance. + * + * Note that the listener is removed automatically when + * {@link OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}. + */ + public static void addOnUpdateAddressListener(OnUpdateAddressListener listener) { + sOnUpdateAddressListeners.add(listener); + } + + /** + * Remove an {@link OnUpdateAddressListener} instance if it exists. + * + * Note that the listener will be removed automatically when + * {@link OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}. + */ + public static void removeOnUpdateAddressListener(OnUpdateAddressListener listener) { + sOnUpdateAddressListeners.remove(listener); + } + /** Returns the current country. */ @NonNull public static synchronized String getCurrentCountry(Context context) { @@ -92,6 +131,17 @@ public class LocationUtils { } catch (Exception e) { // Do nothing } + Set<OnUpdateAddressListener> listenersToRemove = new HashSet<>(); + synchronized (sOnUpdateAddressListeners) { + for (OnUpdateAddressListener listener : sOnUpdateAddressListeners) { + if (listener.onUpdateAddress(sAddress)) { + listenersToRemove.add(listener); + } + } + for (OnUpdateAddressListener listener : listenersToRemove) { + removeOnUpdateAddressListener(listener); + } + } } else { if (DEBUG) Log.d(TAG, "No address returned"); } diff --git a/common/src/com/android/tv/common/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java index 91f2bcd1..3c94aed6 100644 --- a/common/src/com/android/tv/common/util/NetworkTrafficTags.java +++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java @@ -43,19 +43,16 @@ public final class NetworkTrafficTags { @Override public void execute(final @NonNull Runnable command) { - // TODO(b/62038127): robolectric does not support lamdas in unbundled apps - delegateExecutor.execute( - new Runnable() { - @Override - public void run() { - TrafficStats.setThreadStatsTag(tag); - try { - command.run(); - } finally { - TrafficStats.clearThreadStatsTag(); - } - } - }); + // TODO(b/62038127): robolectric does not support lamdas in unbundled apps + delegateExecutor.execute( + () -> { + TrafficStats.setThreadStatsTag(tag); + try { + command.run(); + } finally { + TrafficStats.clearThreadStatsTag(); + } + }); } } diff --git a/common/src/com/android/tv/common/util/PermissionUtils.java b/common/src/com/android/tv/common/util/PermissionUtils.java index 8d409e50..ca1abdc4 100644 --- a/common/src/com/android/tv/common/util/PermissionUtils.java +++ b/common/src/com/android/tv/common/util/PermissionUtils.java @@ -65,4 +65,9 @@ public class PermissionUtils { return context.checkSelfPermission("android.permission.INTERNET") == PackageManager.PERMISSION_GRANTED; } + + public static boolean hasWriteExternalStorage(Context context) { + return context.checkSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE") + == PackageManager.PERMISSION_GRANTED; + } } diff --git a/common/src/com/android/tv/common/util/StringUtils.java b/common/src/com/android/tv/common/util/StringUtils.java index b9461426..bc826208 100644 --- a/common/src/com/android/tv/common/util/StringUtils.java +++ b/common/src/com/android/tv/common/util/StringUtils.java @@ -31,4 +31,9 @@ public final class StringUtils { } return a.compareTo(b); } + + /** Returns {@code s} or {@code ""} if {@code s} is {@code null} */ + public static final String nullToEmpty(String s) { + return s == null ? "" : s; + } } diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java index a9f18d4b..6ac2907b 100644 --- a/common/src/com/android/tv/common/util/SystemProperties.java +++ b/common/src/com/android/tv/common/util/SystemProperties.java @@ -40,6 +40,10 @@ public final class SystemProperties { public static final BooleanSystemProperty USE_TRACKER = new BooleanSystemProperty("tv_use_tracker", true); + /** Allow third party inputs. */ + public static final BooleanSystemProperty ALLOW_THIRD_PARTY_INPUTS = + new BooleanSystemProperty("ro.tv_allow_third_party_inputs", true); + static { updateSystemProperties(); } |