aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/util/PipInputManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/util/PipInputManager.java')
-rw-r--r--src/com/android/tv/util/PipInputManager.java411
1 files changed, 411 insertions, 0 deletions
diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java
new file mode 100644
index 00000000..d5817907
--- /dev/null
+++ b/src/com/android/tv/util/PipInputManager.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+import android.content.Context;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputManager.TvInputCallback;
+import android.util.Log;
+
+import com.android.tv.ChannelTuner;
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that manages inputs for PIP. All tuner inputs are represented to one tuner input for PIP.
+ */
+public class PipInputManager {
+ private static final String TAG = "PipInputManager";
+
+ // Tuner inputs aren't distinguished each other in PipInput. They are handled as one input.
+ // Therefore, we define a fake input id for the unified input.
+ private static final String TUNER_INPUT_ID = "tuner_input_id";
+
+ private final Context mContext;
+ private final TvInputManagerHelper mInputManager;
+ private final ChannelTuner mChannelTuner;
+ private boolean mStarted;
+ private final Map<String, PipInput> mPipInputMap = new HashMap<>(); // inputId -> PipInput
+ private final Set<Listener> mListeners = new HashSet<>();
+
+ private final TvInputCallback mTvInputCallback = new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ if (input.isPassthroughInput()) {
+ boolean available = mInputManager.getInputState(input)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ mPipInputMap.put(inputId, new PipInput(inputId, available));
+ } else if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
+ boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
+ mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
+ } else {
+ return;
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ PipInput pipInput = mPipInputMap.remove(inputId);
+ if (pipInput == null) {
+ if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
+ Log.w(TAG, "A TV input (" + inputId + ") isn't tracked in PipInputManager");
+ return;
+ }
+ if (mInputManager.getTunerTvInputSize() > 0) {
+ return;
+ }
+ mPipInputMap.remove(TUNER_INPUT_ID);
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ @Override
+ public void onInputStateChanged(String inputId, int state) {
+ PipInput pipInput = mPipInputMap.get(inputId);
+ if (pipInput == null) {
+ // For tuner input, state change is handled in mChannelTunerListener.
+ return;
+ }
+ pipInput.updateAvailability();
+ }
+ };
+
+ private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
+ @Override
+ public void onLoadFinished() { }
+
+ @Override
+ public void onCurrentChannelUnavailable(Channel channel) { }
+
+ @Override
+ public void onBrowsableChannelListChanged() {
+ PipInput tunerInput = mPipInputMap.get(TUNER_INPUT_ID);
+ if (tunerInput == null) {
+ return;
+ }
+ tunerInput.updateAvailability();
+ }
+
+ @Override
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+ if (previousChannel != null && currentChannel != null
+ && !previousChannel.isPassthrough() && !currentChannel.isPassthrough()) {
+ // Channel change between channels for tuner inputs.
+ return;
+ }
+ PipInput previousMainInput = getPipInput(previousChannel);
+ if (previousMainInput != null) {
+ previousMainInput.updateAvailability();
+ }
+ PipInput currentMainInput = getPipInput(currentChannel);
+ if (currentMainInput != null) {
+ currentMainInput.updateAvailability();
+ }
+ }
+ };
+
+ public PipInputManager(Context context, TvInputManagerHelper inputManager,
+ ChannelTuner channelTuner) {
+ mContext = context;
+ mInputManager = inputManager;
+ mChannelTuner = channelTuner;
+ }
+
+ /**
+ * Starts {@link PipInputManager}.
+ */
+ public void start() {
+ if (mStarted) {
+ return;
+ }
+ mInputManager.addCallback(mTvInputCallback);
+ mChannelTuner.addListener(mChannelTunerListener);
+ initializePipInputList();
+ }
+
+ /**
+ * Stops {@link PipInputManager}.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ mInputManager.removeCallback(mTvInputCallback);
+ mChannelTuner.removeListener(mChannelTunerListener);
+ mPipInputMap.clear();
+ }
+
+ /**
+ * Adds a {@link PipInputManager.Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a {@link PipInputManager.Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Gets the size of inputs for PIP.
+ *
+ * @param availableOnly If true, it counts only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public int getPipInputSize(boolean availableOnly) {
+ if (availableOnly) {
+ int count = 0;
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (pipInput.isAvailable()) {
+ ++count;
+ }
+ }
+ return count;
+ } else {
+ return mPipInputMap.size();
+ }
+ }
+
+ /**
+ * Gets the list of inputs for PIP.
+ *
+ * @param availableOnly If true, it returns only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public List<PipInput> getPipInputList(boolean availableOnly) {
+ List<PipInput> pipInputs;
+ if (availableOnly) {
+ pipInputs = new ArrayList<>();
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (pipInput.mAvailable) {
+ pipInputs.add(pipInput);
+ }
+ }
+ } else {
+ pipInputs = new ArrayList<>(mPipInputMap.values());
+ }
+ Collections.sort(pipInputs, new Comparator<PipInput>() {
+ @Override
+ public int compare(PipInput lhs, PipInput rhs) {
+ if (!lhs.mIsPassthrough) {
+ return -1;
+ }
+ if (!rhs.mIsPassthrough) {
+ return 1;
+ }
+ String a = lhs.getLabel();
+ String b = rhs.getLabel();
+ return a.compareTo(b);
+ }
+ });
+ return pipInputs;
+ }
+
+ /**
+ * Returns an PIP input corresponding to {@code channel}.
+ */
+ public PipInput getPipInput(Channel channel) {
+ if (channel == null) {
+ return null;
+ }
+ if (channel.isPassthrough()) {
+ return mPipInputMap.get(channel.getInputId());
+ } else {
+ return mPipInputMap.get(TUNER_INPUT_ID);
+ }
+ }
+
+ /**
+ * Returns true, if {@code channel1} and {@code channel2} belong to the same input. For example,
+ * two channels from different tuner inputs are also in the same input "Tuner" from PIP
+ * point of view.
+ */
+ public boolean areInSamePipInput(Channel channel1, Channel channel2) {
+ PipInput input1 = getPipInput(channel1);
+ PipInput input2 = getPipInput(channel2);
+ return input1 != null && input2 != null
+ && getPipInput(channel1).equals(getPipInput(channel2));
+ }
+
+ private void initializePipInputList() {
+ boolean hasTunerInput = false;
+ for (TvInputInfo input : mInputManager.getTvInputInfos(false, false)) {
+ if (input.isPassthroughInput()) {
+ boolean available = mInputManager.getInputState(input)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ mPipInputMap.put(input.getId(), new PipInput(input.getId(), available));
+ } else if (!hasTunerInput) {
+ hasTunerInput = true;
+ boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
+ mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
+ }
+ }
+ PipInput input = getPipInput(mChannelTuner.getCurrentChannel());
+ if (input != null) {
+ input.updateAvailability();
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ /**
+ * Listeners to notify PIP input state changes.
+ */
+ public interface Listener {
+ /**
+ * Called when the state (availability) of PIP inputs is changed.
+ */
+ void onPipInputStateUpdated();
+
+ /**
+ * Called when the list of PIP inputs is changed.
+ */
+ void onPipInputListUpdated();
+ }
+
+ /**
+ * Input class for PIP. It has useful methods for PIP handling.
+ */
+ public class PipInput {
+ private final String mInputId;
+ private final boolean mIsPassthrough;
+ private final TvInputInfo mInputInfo;
+ private boolean mAvailable;
+
+ private PipInput(String inputId, boolean available) {
+ mInputId = inputId;
+ mIsPassthrough = !mInputId.equals(TUNER_INPUT_ID);
+ if (mIsPassthrough) {
+ mInputInfo = mInputManager.getTvInputInfo(mInputId);
+ } else {
+ mInputInfo = null;
+ }
+ mAvailable = available;
+ }
+
+ /**
+ * Returns the {@link TvInputInfo} object that matches to this PIP input.
+ */
+ public TvInputInfo getInputInfo() {
+ return mInputInfo;
+ }
+
+ /**
+ * Returns true, if the input is available for PIP. If a channel of an input is already
+ * played or an input is not connected state or there is no browsable channel, the input
+ * is unavailable.
+ */
+ public boolean isAvailable() {
+ return mAvailable;
+ }
+
+ /**
+ * Returns true, if the input is a passthrough TV input.
+ */
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ /**
+ * Gets a channel to play in a PIP view.
+ */
+ public Channel getChannel() {
+ if (mIsPassthrough) {
+ return Channel.createPassthroughChannel(mInputId);
+ } else {
+ return mChannelTuner.findNearestBrowsableChannel(
+ Utils.getLastWatchedChannelId(mContext));
+ }
+ }
+
+ /**
+ * Gets a label of the input.
+ */
+ public String getLabel() {
+ if (mIsPassthrough) {
+ return mInputInfo.loadLabel(mContext).toString();
+ } else {
+ return mContext.getString(R.string.input_selector_tuner_label);
+ }
+ }
+
+ /**
+ * Gets a long label including a customized label.
+ */
+ public String getLongLabel() {
+ if (mIsPassthrough) {
+ String customizedLabel = Utils.loadLabel(mContext, mInputInfo);
+ String label = getLabel();
+ if (label.equals(customizedLabel)) {
+ return customizedLabel;
+ }
+ return customizedLabel + " (" + label + ")";
+ } else {
+ return mContext.getString(R.string.input_long_label_for_tuner);
+ }
+ }
+
+ /**
+ * Updates availability. It returns true, if availability is changed.
+ */
+ private void updateAvailability() {
+ boolean available;
+ // current playing input cannot be available for PIP.
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ if (mIsPassthrough) {
+ if (currentChannel != null && currentChannel.getInputId().equals(mInputId)) {
+ available = false;
+ } else {
+ available = mInputManager.getInputState(mInputId)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ }
+ } else {
+ if (currentChannel != null && !currentChannel.isPassthrough()) {
+ available = false;
+ } else {
+ available = mChannelTuner.getBrowsableChannelCount() > 0;
+ }
+ }
+ if (mAvailable != available) {
+ mAvailable = available;
+ for (Listener l : mListeners) {
+ l.onPipInputStateUpdated();
+ }
+ }
+ }
+ }
+}