diff options
Diffstat (limited to 'src/com/android/tv/util/PipInputManager.java')
-rw-r--r-- | src/com/android/tv/util/PipInputManager.java | 411 |
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(); + } + } + } + } +} |